<template>
<div class="component-card-demo-wrap">
<TsDropdown :list="dropList" v-model="dropValue" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDropdown } from 'tui';
const dropValue = ref(1);
const dropList = [
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2 },
{ label: '选项3', value: 3 },
];
</script>
<template>
<TsDropdown :list="descList" v-model="descValue" width="300"></TsDropdown>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const descValue = ref(1);
const descList = [
{ label: '基础版', value: 1, desc: '适合个人用户和小团队' },
{ label: '专业版', value: 2, desc: '适合中小型企业,功能更全面' },
{ label: '企业版', value: 3, desc: '适合大型企业,定制化服务' },
];
</script>
禁用状态
禁用特定选项
<template>
<div class="dropdown-disabled-demo">
<div class="demo-section">
<p class="demo-title">禁用状态</p>
<div class="demo-controls">
<TsDropdown :list="dropList" v-model="dropValue" disabled placeholder="禁用的下拉框" />
</div>
</div>
<div class="demo-section">
<p class="demo-title">禁用特定选项</p>
<div class="demo-controls">
<TsDropdown :list="dropListWithDisabled" v-model="dropValueWithDisabled" placeholder="包含禁用选项" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDropdown } from 'tui';
const dropValue = ref(1);
const dropValueWithDisabled = ref(1);
const dropList = [
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2 },
{ label: '选项3', value: 3 },
];
const dropListWithDisabled = [
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2, disabled: true },
{ label: '选项3', value: 3 },
{ label: '选项4', value: 4, disabled: true },
];
</script>
<style scoped>
.dropdown-disabled-demo {
max-width: 400px;
}
.demo-section {
margin-bottom: 24px;
}
.demo-title {
font-weight: bold;
margin-bottom: 12px;
color: var(--ts-color-text);
}
.demo-controls {
margin-bottom: 16px;
}
</style>
<template>
<div class="component-card-demo-wrap">
<TsDropdown v-model="dropValue" :nowrap="true" width="320" :list="dropList">
<template #arrow>
<BussinessIcon name="timeszicon-xiasanjiao" font-size="16" />
</template>
</TsDropdown>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDropdown } from 'tui';
import BussinessIcon from '@/components/BussinessIcon.vue';
const dropValue = ref(1);
const dropList = [
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2 },
{ label: '选项3', value: 3 },
];
</script>
不同位置
bottom-start (默认)
top-start
bottom-end
top-end
left-center
right-center
<template>
<div class="dropdown-position-demo">
<div class="demo-section">
<p class="demo-title">不同位置</p>
<div class="position-grid">
<div class="position-item">
<p class="position-label">bottom-start (默认)</p>
<TsDropdown :list="dropList" v-model="position1" position="bottom-start" />
</div>
<div class="position-item">
<p class="position-label">top-start</p>
<TsDropdown :list="dropList" v-model="position2" position="top-start" />
</div>
<div class="position-item">
<p class="position-label">bottom-end</p>
<TsDropdown :list="dropList" v-model="position3" position="bottom-end" />
</div>
<div class="position-item">
<p class="position-label">top-end</p>
<TsDropdown :list="dropList" v-model="position4" position="top-end" />
</div>
<div class="position-item">
<p class="position-label">left-center</p>
<TsDropdown :list="dropList" v-model="position5" position="left-center" />
</div>
<div class="position-item">
<p class="position-label">right-center</p>
<TsDropdown :list="dropList" v-model="position6" position="right-center" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDropdown } from 'tui';
const position1 = ref(1);
const position2 = ref(1);
const position3 = ref(1);
const position4 = ref(1);
const position5 = ref(1);
const position6 = ref(1);
const dropList = [
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2 },
{ label: '选项3', value: 3 },
];
</script>
<style scoped>
.dropdown-position-demo {
max-width: 800px;
}
.demo-section {
margin-bottom: 24px;
}
.demo-title {
font-weight: bold;
margin-bottom: 16px;
color: var(--ts-color-text);
}
.position-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 24px;
margin-bottom: 16px;
}
.position-item {
text-align: center;
}
.position-label {
font-size: 12px;
color: #666;
margin-bottom: 8px;
font-weight: 500;
}
</style>
自定义内容插槽
顶部 / 底部插槽
<template>
<div class="dropdown-custom-demo">
<div class="demo-section">
<p class="demo-title">自定义内容插槽</p>
<div class="demo-controls">
<TsDropdown :list="contentList" v-model="contentValue" width="360">
<template #icon>
<TsIcon name="ArrowDownIcon" fontSize="16px" />
</template>
<template #content="{ item }">
<div class="custom-content">
<div class="content-icon">
<TsIcon :name="item.icon" fontSize="16px" />
</div>
<div class="content-info">
<div class="content-title">{{ item.label }}</div>
<div class="content-subtitle">{{ item.subtitle }}</div>
</div>
<div class="content-meta">
<span class="content-badge" :class="`is-${item.status}`">
{{ statusLabels[(item as ContentItem).status] || item.status }}
</span>
</div>
</div>
</template>
</TsDropdown>
</div>
</div>
<div class="demo-section">
<p class="demo-title">顶部 / 底部插槽</p>
<div class="demo-controls">
<TsDropdown :list="frameworkList" v-model="frameworkValue" width="340">
<template #icon>
<TsIcon name="ArrowDownIcon" fontSize="16px" />
</template>
<template #top>
<div class="dropdown-top">
<div>
<div class="top-title">推荐框架</div>
<div class="top-subtitle">选择合适的技术栈</div>
</div>
<div class="top-action">
<TsIcon name="SearchIcon" fontSize="14px" />
<span>筛选</span>
</div>
</div>
</template>
<template #bottom>
<div class="dropdown-bottom">
<span class="bottom-text">共 {{ frameworkList.length }} 项</span>
<div class="bottom-actions">
<TsButton size="small" type="text">管理</TsButton>
<TsIcon name="ArrowRightIcon" fontSize="14px" />
</div>
</div>
</template>
</TsDropdown>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsButton, TsDropdown, TsIcon } from 'tui';
interface ContentItem {
label: string;
value: number;
subtitle: string;
status: 'active' | 'new' | 'updated' | 'pending';
icon: string;
}
interface FrameworkItem {
label: string;
value: number;
desc?: string;
}
const customValue = ref(1);
const contentValue = ref(1);
const frameworkValue = ref(1);
const customList = [
{ label: '🏠 首页', value: 1 },
{ label: '📊 数据分析', value: 2 },
{ label: '⚙️ 系统设置', value: 3 },
{ label: '👤 个人中心', value: 4 },
];
const contentList: ContentItem[] = [
{ label: '数据分析', value: 1, subtitle: '查看关键指标', status: 'active', icon: 'SearchIcon' },
{ label: '内容预览', value: 2, subtitle: '最新页面渲染', status: 'new', icon: 'PreviewIcon' },
{ label: '权限设置', value: 3, subtitle: '角色与权限配置', status: 'updated', icon: 'SelectIcon' },
{ label: '更多操作', value: 4, subtitle: '查看全部工具', status: 'pending', icon: 'MorePointIcon' },
];
const frameworkList: FrameworkItem[] = [
{ label: 'Vue.js', value: 1, desc: '渐进式 JavaScript 框架' },
{ label: 'React', value: 2, desc: '用于构建用户界面的库' },
{ label: 'Angular', value: 3, desc: '基于 TypeScript 的框架' },
{ label: 'TypeScript', value: 4, desc: 'JavaScript 的超集' },
];
const statusLabels: Record<ContentItem['status'], string> = {
active: '运行中',
new: '新增',
updated: '更新',
pending: '待处理',
};
</script>
<style scoped>
.dropdown-custom-demo {
max-width: 820px;
}
.demo-section {
padding: 4px 0 20px;
}
.demo-section + .demo-section {
border-top: 1px dashed rgba(0, 0, 0, 0.06);
padding-top: 20px;
}
.demo-title {
font-weight: 600;
margin-bottom: 10px;
color: var(--ts-color-text);
}
.demo-controls {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
}
.custom-content {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 8px;
}
.content-icon {
width: 32px;
height: 32px;
border-radius: 8px;
background: #fff;
border: 1px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: center;
color: #409eff;
}
.content-info {
flex: 1;
}
.content-title {
font-size: 13px;
font-weight: 600;
color: #1f2937;
}
.content-subtitle {
font-size: 12px;
color: #6b7280;
}
.content-meta {
margin-left: auto;
}
.content-badge {
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid transparent;
white-space: nowrap;
}
.content-badge.is-active {
background: #ecfdf5;
color: #059669;
border-color: #a7f3d0;
}
.content-badge.is-new {
background: #eff6ff;
color: #2563eb;
border-color: #bfdbfe;
}
.content-badge.is-updated {
background: #fff7ed;
color: #ea580c;
border-color: #fed7aa;
}
.content-badge.is-pending {
background: #fdf2f8;
color: #db2777;
border-color: #fbcfe8;
}
.dropdown-top {
padding: 10px 12px;
background: #f5f7fb;
border-bottom: 1px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: space-between;
}
.top-title {
font-size: 13px;
font-weight: 600;
color: #1f2937;
}
.top-subtitle {
font-size: 12px;
color: #6b7280;
}
.top-action {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #409eff;
}
.dropdown-bottom {
padding: 10px 12px;
background: #fafafa;
border-top: 1px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: space-between;
}
.bottom-text {
font-size: 12px;
color: #6b7280;
}
.bottom-actions {
display: inline-flex;
align-items: center;
gap: 6px;
color: #409eff;
}
</style>
事件监听
<template>
<div class="dropdown-event-demo">
<div class="demo-section">
<p class="demo-title">事件监听</p>
<div class="demo-controls">
<TsDropdown :list="dropList" v-model="dropValue" @change="handleChange" />
</div>
<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-value">{{ log.label }}</span>
<span class="log-action">{{ log.action }}</span>
</div>
</div>
<div class="demo-actions">
<TsButton @click="clearLogs" size="small">清空记录</TsButton>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsDropdown, TsButton } from 'tui';
const dropValue = ref(1);
const eventLogs = ref<Array<{time: string, label: string, action: string}>>([]);
const dropList = [
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2 },
{ label: '选项3', value: 3 },
];
const handleChange = (item: any) => {
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,
label: item.label,
action: '选择'
});
// 只保留最近10条记录
if (eventLogs.value.length > 10) {
eventLogs.value = eventLogs.value.slice(0, 10);
}
};
const clearLogs = () => {
eventLogs.value = [];
};
</script>
<style scoped>
.dropdown-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-log {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 16px;
margin-bottom: 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-value {
color: #1890ff;
font-weight: 500;
margin-right: 12px;
min-width: 80px;
}
.log-action {
color: #52c41a;
font-size: 12px;
}
.demo-actions {
margin-top: 16px;
}
</style>