<template>
<div class="component-card-demo-wrap">
<TsButton @click="handleOpenDrawerPlacement('bottom')">打开抽屉 bottom</TsButton>
<TsButton @click="handleOpenDrawerPlacement('right')">打开抽屉 right</TsButton>
<TsButton @click="handleOpenDrawerPlacement('top')">打开抽屉 top</TsButton>
<TsButton @click="handleOpenDrawerPlacement('left')">打开抽屉 left</TsButton>
<TsDrawer v-model="drawerOpen" :placement="drawerPlacement" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDrawer, TsButton } from 'tui';
const drawerOpen = ref(false);
const drawerPlacement = ref<'top' | 'right' | 'bottom' | 'left'>('right');
const handleOpenDrawerPlacement = (placement: 'top' | 'right' | 'bottom' | 'left') => {
drawerPlacement.value = placement;
drawerOpen.value = true;
};
</script>
自定义尺寸和样式
这是一个自定义尺寸的抽屉,宽度为 600px,高度为 80vh。
禁用了默认的关闭按钮和遮罩点击关闭功能。
<template>
<div class="drawer-custom-demo">
<div class="demo-section">
<p class="demo-title">自定义尺寸和样式</p>
<div class="demo-controls">
<TsButton @click="openCustomDrawer">打开自定义抽屉</TsButton>
</div>
<TsDrawer
v-model="customDrawerOpen"
width="600px"
height="80vh"
placement="right"
:show-close="false"
:close-on-click-mask="false"
>
<div class="custom-content">
<div class="custom-header">
<h3>自定义抽屉内容</h3>
<button @click="customDrawerOpen = false" class="custom-close">×</button>
</div>
<div class="custom-body">
<p>这是一个自定义尺寸的抽屉,宽度为 600px,高度为 80vh。</p>
<p>禁用了默认的关闭按钮和遮罩点击关闭功能。</p>
<div class="form-content">
<div class="form-group">
<label>标题</label>
<input type="text" placeholder="请输入标题" />
</div>
<div class="form-group">
<label>描述</label>
<textarea placeholder="请输入描述" rows="4"></textarea>
</div>
<div class="form-actions">
<button class="btn-primary">保存</button>
<button class="btn-secondary" @click="customDrawerOpen = false">取消</button>
</div>
</div>
</div>
</div>
</TsDrawer>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDrawer, TsButton } from 'tui';
const customDrawerOpen = ref(false);
const openCustomDrawer = () => {
customDrawerOpen.value = true;
};
</script>
<style scoped>
.drawer-custom-demo {
max-width: 800px;
}
.demo-section {
margin-bottom: 24px;
}
.demo-title {
font-weight: bold;
margin-bottom: 12px;
color: var(--ts-color-text);
}
.demo-controls {
margin-bottom: 16px;
}
.custom-content {
height: 100%;
display: flex;
flex-direction: column;
}
.custom-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid #e8e8e8;
}
.custom-header h3 {
margin: 0;
font-size: 18px;
color: #333;
}
.custom-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.custom-close:hover {
background: #f5f5f5;
color: #666;
}
.custom-body {
flex: 1;
padding: 24px;
overflow-y: auto;
}
.custom-body p {
margin-bottom: 16px;
line-height: 1.6;
color: #666;
}
.form-content {
margin-top: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
}
.form-group input: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;
gap: 12px;
margin-top: 24px;
}
.btn-primary {
padding: 10px 20px;
background: #1890ff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
.btn-secondary {
padding: 10px 20px;
background: #f5f5f5;
color: #666;
border: 1px solid #d9d9d9;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
.btn-primary:hover {
background: #40a9ff;
}
.btn-secondary:hover {
background: #e8e8e8;
}
</style>
表单抽屉
<template>
<div class="drawer-form-demo">
<div class="demo-section">
<p class="demo-title">表单抽屉</p>
<div class="demo-controls">
<TsButton @click="openFormDrawer">打开表单抽屉</TsButton>
</div>
<TsDrawer
v-model="formDrawerOpen"
width="500px"
placement="right"
title="用户信息"
@close="handleFormClose"
>
<div class="form-drawer-content">
<div class="form-section">
<h4>基本信息</h4>
<div class="form-row">
<div class="form-item">
<label>姓名 *</label>
<input v-model="formData.name" type="text" placeholder="请输入姓名" />
</div>
<div class="form-item">
<label>邮箱 *</label>
<input v-model="formData.email" type="email" placeholder="请输入邮箱" />
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>电话</label>
<input v-model="formData.phone" type="tel" placeholder="请输入电话" />
</div>
<div class="form-item">
<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>
</div>
<div class="form-section">
<h4>附加信息</h4>
<div class="form-item full-width">
<label>备注</label>
<textarea v-model="formData.remark" placeholder="请输入备注信息" rows="3"></textarea>
</div>
</div>
<div class="form-actions">
<TsButton @click="handleSave" type="primary">保存</TsButton>
<TsButton @click="formDrawerOpen = false">取消</TsButton>
</div>
</div>
</TsDrawer>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { TsDrawer, TsButton } from 'tui';
const formDrawerOpen = ref(false);
const formData = reactive({
name: '',
email: '',
phone: '',
department: '',
remark: ''
});
const openFormDrawer = () => {
formDrawerOpen.value = true;
};
const handleSave = () => {
console.log('保存表单数据:', formData);
// 这里可以添加保存逻辑
formDrawerOpen.value = false;
};
const handleFormClose = () => {
console.log('表单抽屉关闭');
};
</script>
<style scoped>
.drawer-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-drawer-content {
padding: 20px;
}
.form-section {
margin-bottom: 24px;
}
.form-section h4 {
margin: 0 0 16px 0;
font-size: 16px;
color: #333;
border-bottom: 1px solid #e8e8e8;
padding-bottom: 8px;
}
.form-row {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
.form-item {
flex: 1;
}
.form-item.full-width {
width: 100%;
}
.form-item label {
display: block;
margin-bottom: 6px;
font-size: 14px;
font-weight: 500;
color: #333;
}
.form-item input,
.form-item select,
.form-item 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-item input:focus,
.form-item select:focus,
.form-item 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: 24px;
padding-top: 16px;
border-top: 1px solid #e8e8e8;
}
</style>
嵌套抽屉
这是第一层抽屉的内容。
这是第二层抽屉的内容。
<template>
<div class="drawer-nested-demo">
<div class="demo-section">
<p class="demo-title">嵌套抽屉</p>
<div class="demo-controls">
<TsButton @click="openFirstDrawer">打开第一层抽屉</TsButton>
<TsButton @click="openSecondDrawer">打开第二层抽屉</TsButton>
</div>
<!-- 第一层抽屉 -->
<TsDrawer
v-model="firstDrawerOpen"
width="400px"
placement="right"
title="第一层抽屉"
>
<div class="nested-content">
<p>这是第一层抽屉的内容。</p>
<div class="nested-actions">
<TsButton @click="secondDrawerOpen = true" size="small">打开第二层抽屉</TsButton>
<TsButton @click="firstDrawerOpen = false" size="small">关闭</TsButton>
</div>
<div class="info-box">
<h5>操作说明</h5>
<ul>
<li>点击按钮可以在当前抽屉内打开第二层抽屉</li>
<li>第二层抽屉会覆盖在第一层抽屉之上</li>
<li>支持多层嵌套,但建议不超过3层</li>
</ul>
</div>
</div>
</TsDrawer>
<!-- 第二层抽屉 -->
<TsDrawer
v-model="secondDrawerOpen"
width="350px"
placement="right"
title="第二层抽屉"
@close="handleSecondDrawerClose"
>
<div class="nested-content">
<p>这是第二层抽屉的内容。</p>
<div class="form-item">
<label>操作类型</label>
<select v-model="operationType">
<option value="">请选择操作</option>
<option value="edit">编辑</option>
<option value="delete">删除</option>
<option value="export">导出</option>
</select>
</div>
<div class="form-item">
<label>备注说明</label>
<textarea v-model="operationRemark" placeholder="请输入操作备注" rows="3"></textarea>
</div>
<div class="nested-actions">
<TsButton @click="handleNestedOperation" size="small" type="primary">确认操作</TsButton>
<TsButton @click="secondDrawerOpen = false" size="small">关闭</TsButton>
</div>
</div>
</TsDrawer>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDrawer, TsButton } from 'tui';
const firstDrawerOpen = ref(false);
const secondDrawerOpen = ref(false);
const operationType = ref('');
const operationRemark = ref('');
const openFirstDrawer = () => {
firstDrawerOpen.value = true;
};
const openSecondDrawer = () => {
secondDrawerOpen.value = true;
};
const handleSecondDrawerClose = () => {
console.log('第二层抽屉关闭');
operationType.value = '';
operationRemark.value = '';
};
const handleNestedOperation = () => {
console.log('嵌套操作:', {
type: operationType.value,
remark: operationRemark.value
});
secondDrawerOpen.value = false;
};
</script>
<style scoped>
.drawer-nested-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;
}
.nested-content {
padding: 20px;
}
.nested-content p {
margin-bottom: 16px;
color: #666;
line-height: 1.5;
}
.nested-actions {
margin-bottom: 20px;
}
.info-box {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 16px;
margin-top: 16px;
}
.info-box h5 {
margin: 0 0 8px 0;
font-size: 14px;
color: #333;
}
.info-box ul {
margin: 0;
padding-left: 20px;
}
.info-box li {
margin-bottom: 6px;
color: #666;
font-size: 13px;
}
.form-item {
margin-bottom: 16px;
}
.form-item label {
display: block;
margin-bottom: 6px;
font-size: 14px;
font-weight: 500;
color: #333;
}
.form-item select,
.form-item textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
}
.nested-actions {
display: flex;
gap: 8px;
margin-top: 20px;
}
</style>
事件监听
<template>
<div class="drawer-event-demo">
<div class="demo-section">
<p class="demo-title">事件监听</p>
<div class="demo-controls">
<TsButton @click="openEventDrawer">打开事件抽屉</TsButton>
</div>
<TsDrawer
v-model="eventDrawerOpen"
width="450px"
placement="right"
title="事件演示"
@close="handleClose"
@open="handleOpen"
>
<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="eventDrawerOpen = false" size="small">关闭抽屉</TsButton>
</div>
</div>
</TsDrawer>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDrawer, TsButton } from 'tui';
const eventDrawerOpen = ref(false);
const eventLogs = ref<Array<{time: string, type: string, message: string}>>([]);
const openEventDrawer = () => {
eventDrawerOpen.value = true;
addLog('open', '抽屉打开');
};
const handleClose = () => {
addLog('close', '抽屉关闭');
};
const handleOpen = () => {
addLog('open', '抽屉打开事件触发');
};
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>
.drawer-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;
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>