v-model 控制模态框的显示和隐藏。<template>
<div class="component-card-demo-wrap">
<TsButton @click="openBaseModal">打开模态框</TsButton>
<TsModal v-model="baseModalVisible" title="标题" content="内容"></TsModal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsModal } from 'tui';
const baseModalVisible = ref(false);
const openBaseModal = () => {
baseModalVisible.value = true;
};
</script>
title 和 content 属性快速定义模态框内容。<template>
<div class="component-card-demo-wrap">
<TsButton @click="openSimpleModal">打开模态框</TsButton>
<TsModal v-model="simpleModalVisible" title="This is a title" content="this is a modal"></TsModal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsModal } from 'tui';
const simpleModalVisible = ref(false);
const openSimpleModal = () => {
simpleModalVisible.value = true;
};
</script>
title 和 content 插槽可自定义展示样式和结构。<template>
<div class="component-card-demo-wrap">
<TsButton @click="openSlotModal">打开模态框</TsButton>
<TsModal v-model="slotModalVisible">
<template #title>
<div class="modal-title">标题</div>
</template>
<template #content>
<div class="modal-content">这是模态框的内容区域,可以放置文本或表单。</div>
</template>
</TsModal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsModal } from 'tui';
const slotModalVisible = ref(false);
const openSlotModal = () => {
slotModalVisible.value = true;
};
</script>
close 插槽自定义关闭按钮,替代默认的右上角关闭图标。<template>
<div class="component-card-demo-wrap">
<TsButton @click="openCloseSlotModal">打开模态框</TsButton>
<TsModal v-model="closeSlotModalVisible" :showClose="true">
<template #title>
<div class="modal-title">带自定义关闭按钮</div>
</template>
<template #close>
<TsButton type="danger" size="small" @click="closeSlotModalVisible = false">X</TsButton>
</template>
<template #content>
<div class="modal-content">你可以通过自定义 close 插槽替换默认关闭按钮。</div>
</template>
</TsModal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsModal } from 'tui';
const closeSlotModalVisible = ref(false);
const openCloseSlotModal = () => {
closeSlotModalVisible.value = true;
};
</script>
width 和 height 设置模态框尺寸,设置 fixed 属性可固定在页面中央。<template>
<div class="component-card-demo-wrap">
<TsButton @click="openFixedModal">打开模态框</TsButton>
<TsModal v-model="fixedModalVisible" width="600px" height="300px" :fixed="true">
<template #title>
<div class="modal-title">大号模态框</div>
</template>
<template #content>
<div class="modal-content">这是一个宽 600px,高 300px,并且固定在页面上的模态框。</div>
</template>
</TsModal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsModal } from 'tui';
const fixedModalVisible = ref(false);
const openFixedModal = () => {
fixedModalVisible.value = true;
};
</script>
closeOnClickMask 属性控制是否允许点击遮罩层关闭模态框。<template>
<div class="component-card-demo-wrap">
<TsButton @click="openForceModal">打开模态框</TsButton>
<TsModal v-model="forceModalVisible" :closeOnClickMask="false" :showClose="false">
<template #title>
<div class="modal-title">强制交互</div>
</template>
<template #content>
<div class="modal-content">你必须点击底部按钮、或者关闭图标才能关闭,而不是点击遮罩层。</div>
</template>
<template #footer>
<div class="modal-footer">
<TsButton @click="forceModalVisible = false">我觉得应该点取消</TsButton>
<TsButton type="primary" @click="forceModalVisible = false">我知道了</TsButton>
</div>
</template>
</TsModal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsModal } from 'tui';
const forceModalVisible = ref(false);
const openForceModal = () => {
forceModalVisible.value = true;
};
</script>
表单模态框
<template>
<div class="modal-form-demo">
<div class="demo-section">
<p class="demo-title">表单模态框</p>
<div class="demo-controls">
<TsButton @click="openFormModal">打开表单模态框</TsButton>
</div>
<TsModal v-model="formModalVisible" width="500px" title="用户信息">
<template #content>
<div class="form-content">
<div class="form-group">
<label>姓名 *</label>
<input v-model="formData.name" type="text" placeholder="请输入姓名" />
</div>
<div class="form-group">
<label>邮箱 *</label>
<input v-model="formData.email" type="email" placeholder="请输入邮箱" />
</div>
<div class="form-group">
<label>电话</label>
<input v-model="formData.phone" type="tel" placeholder="请输入电话" />
</div>
<div class="form-group">
<label>部门</label>
<select v-model="formData.department">
<option value="">请选择部门</option>
<option value="tech">技术部</option>
<option value="product">产品部</option>
<option value="design">设计部</option>
</select>
</div>
<div class="form-group">
<label>备注</label>
<textarea v-model="formData.remark" placeholder="请输入备注信息" rows="3"></textarea>
</div>
</div>
</template>
<template #footer>
<div class="form-actions">
<TsButton @click="handleCancel">取消</TsButton>
<TsButton type="primary" @click="handleSave">保存</TsButton>
</div>
</template>
</TsModal>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { TsButton, TsModal } from 'tui';
const formModalVisible = ref(false);
const formData = reactive({
name: '',
email: '',
phone: '',
department: '',
remark: ''
});
const openFormModal = () => {
formModalVisible.value = true;
};
const handleCancel = () => {
formModalVisible.value = false;
resetForm();
};
const handleSave = () => {
console.log('保存表单数据:', formData);
// 这里可以添加保存逻辑
formModalVisible.value = false;
resetForm();
};
const resetForm = () => {
Object.assign(formData, {
name: '',
email: '',
phone: '',
department: '',
remark: ''
});
};
</script>
<style scoped>
.modal-form-demo {
max-width: 700px;
}
.demo-section {
margin-bottom: 24px;
}
.demo-title {
font-weight: bold;
margin-bottom: 12px;
color: var(--ts-color-text);
}
.demo-controls {
margin-bottom: 16px;
}
.form-content {
padding: 20px 0;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-size: 14px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
transition: border-color 0.2s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid #e8e8e8;
}
</style>
确认对话框
确定要删除这个项目吗?
删除后将无法恢复,请谨慎操作。
确定要保存当前修改吗?
保存后将覆盖之前的版本。
当前操作可能存在风险,请确认是否继续?
建议先备份数据再进行操作。
<template>
<div class="modal-confirm-demo">
<div class="demo-section">
<p class="demo-title">确认对话框</p>
<div class="demo-controls">
<TsButton @click="openDeleteConfirm">删除确认</TsButton>
<TsButton @click="openSaveConfirm">保存确认</TsButton>
<TsButton @click="openWarningConfirm">警告确认</TsButton>
</div>
<!-- 删除确认 -->
<TsModal v-model="deleteConfirmVisible" width="400px" :showClose="false" :closeOnClickMask="false">
<template #title>
<div class="confirm-title delete">
<span class="icon">⚠️</span>
删除确认
</div>
</template>
<template #content>
<div class="confirm-content">
<p>确定要删除这个项目吗?</p>
<p class="warning-text">删除后将无法恢复,请谨慎操作。</p>
</div>
</template>
<template #footer>
<div class="confirm-actions">
<TsButton @click="deleteConfirmVisible = false">取消</TsButton>
<TsButton type="danger" @click="handleDelete">确认删除</TsButton>
</div>
</template>
</TsModal>
<!-- 保存确认 -->
<TsModal v-model="saveConfirmVisible" width="400px" :showClose="false" :closeOnClickMask="false">
<template #title>
<div class="confirm-title save">
<span class="icon">💾</span>
保存确认
</div>
</template>
<template #content>
<div class="confirm-content">
<p>确定要保存当前修改吗?</p>
<p>保存后将覆盖之前的版本。</p>
</div>
</template>
<template #footer>
<div class="confirm-actions">
<TsButton @click="saveConfirmVisible = false">取消</TsButton>
<TsButton type="primary" @click="handleSave">确认保存</TsButton>
</div>
</template>
</TsModal>
<!-- 警告确认 -->
<TsModal v-model="warningConfirmVisible" width="400px" :showClose="false" :closeOnClickMask="false">
<template #title>
<div class="confirm-title warning">
<span class="icon">🚨</span>
操作警告
</div>
</template>
<template #content>
<div class="confirm-content">
<p>当前操作可能存在风险,请确认是否继续?</p>
<p class="warning-text">建议先备份数据再进行操作。</p>
</div>
</template>
<template #footer>
<div class="confirm-actions">
<TsButton @click="warningConfirmVisible = false">取消</TsButton>
<TsButton type="warning" @click="handleWarning">继续操作</TsButton>
</div>
</template>
</TsModal>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsModal } from 'tui';
const deleteConfirmVisible = ref(false);
const saveConfirmVisible = ref(false);
const warningConfirmVisible = ref(false);
const openDeleteConfirm = () => {
deleteConfirmVisible.value = true;
};
const openSaveConfirm = () => {
saveConfirmVisible.value = true;
};
const openWarningConfirm = () => {
warningConfirmVisible.value = true;
};
const handleDelete = () => {
console.log('确认删除');
deleteConfirmVisible.value = false;
};
const handleSave = () => {
console.log('确认保存');
saveConfirmVisible.value = false;
};
const handleWarning = () => {
console.log('继续操作');
warningConfirmVisible.value = false;
};
</script>
<style scoped>
.modal-confirm-demo {
max-width: 700px;
}
.demo-section {
margin-bottom: 24px;
}
.demo-title {
font-weight: bold;
margin-bottom: 12px;
color: var(--ts-color-text);
}
.demo-controls {
margin-bottom: 16px;
}
.demo-controls .ts-button {
margin-right: 8px;
}
.confirm-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
}
.confirm-title.delete {
color: #ff4d4f;
}
.confirm-title.save {
color: #1890ff;
}
.confirm-title.warning {
color: #faad14;
}
.icon {
font-size: 20px;
}
.confirm-content {
padding: 20px 0;
}
.confirm-content p {
margin: 0 0 12px 0;
line-height: 1.5;
color: #666;
}
.warning-text {
color: #ff4d4f !important;
font-weight: 500;
}
.confirm-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 16px;
border-top: 1px solid #e8e8e8;
}
</style>
事件监听
<template>
<div class="modal-event-demo">
<div class="demo-section">
<p class="demo-title">事件监听</p>
<div class="demo-controls">
<TsButton @click="openEventModal">打开事件模态框</TsButton>
</div>
<TsModal
v-model="eventModalVisible"
width="500px"
title="事件演示"
@close="handleClose"
>
<template #content>
<div class="event-content">
<div class="event-log">
<h4>事件日志</h4>
<div class="log-item" v-for="(log, index) in eventLogs" :key="index">
<span class="log-time">{{ log.time }}</span>
<span class="log-type" :class="log.type">{{ log.type }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
<div class="demo-actions">
<TsButton @click="clearLogs" size="small">清空日志</TsButton>
<TsButton @click="eventModalVisible = false" size="small">关闭模态框</TsButton>
</div>
</div>
</template>
</TsModal>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsModal } from 'tui';
const eventModalVisible = ref(false);
const eventLogs = ref<Array<{time: string, type: string, message: string}>>([]);
const openEventModal = () => {
eventModalVisible.value = true;
addLog('open', '模态框打开');
};
const handleClose = () => {
addLog('close', '模态框关闭');
};
const addLog = (type: string, message: string) => {
const now = new Date();
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
eventLogs.value.unshift({
time,
type,
message
});
// 只保留最近10条记录
if (eventLogs.value.length > 10) {
eventLogs.value = eventLogs.value.slice(0, 10);
}
};
const clearLogs = () => {
eventLogs.value = [];
};
</script>
<style scoped>
.modal-event-demo {
max-width: 600px;
}
.demo-section {
margin-bottom: 24px;
}
.demo-title {
font-weight: bold;
margin-bottom: 12px;
color: var(--ts-color-text);
}
.demo-controls {
margin-bottom: 16px;
}
.event-content {
padding: 20px 0;
height: 100%;
display: flex;
flex-direction: column;
}
.event-log {
flex: 1;
margin-bottom: 20px;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 16px;
max-height: 300px;
overflow-y: auto;
}
.event-log h4 {
margin: 0 0 12px 0;
font-size: 16px;
color: #333;
}
.log-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #e8e8e8;
font-size: 13px;
}
.log-item:last-child {
border-bottom: none;
}
.log-time {
color: #999;
font-family: monospace;
margin-right: 12px;
min-width: 60px;
}
.log-type {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
margin-right: 12px;
min-width: 60px;
text-align: center;
}
.log-type.open {
background: #e6f7ff;
color: #1890ff;
}
.log-type.close {
background: #fff2f0;
color: #ff4d4f;
}
.log-message {
color: #666;
flex: 1;
}
.demo-actions {
display: flex;
gap: 8px;
margin-top: 16px;
}
</style>