<template>
<div class="icons-all-demo">
<div class="icon-grid">
<div v-for="item in icons" :key="item" class="icon-item" @click="handleCopy(item)">
<div class="icon-wrapper">
<TsIcon :name="item" />
</div>
<div class="icon-name">{{ item }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsIcon, TsToast } from 'tui';
import { navtiveCopy } from 'tui/utils/dom/copyToClipboard';
import iconNames from 'tui/icons';
const icons = ref(iconNames);
const handleCopy = (text: string) => {
navtiveCopy(text);
TsToast({ type: 'success', message: `已复制 ${text}`, useSingleton: false, offset: [0, 60] });
};
</script>
<style scoped>
.icons-all-demo {
max-width: 100%;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 16px;
padding: 20px 0;
}
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: white;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.icon-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.icon-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: #f8f9fa;
}
.icon-item:hover::before {
opacity: 1;
}
.icon-wrapper {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
border-radius: 6px;
margin-bottom: 8px;
transition: all 0.2s ease;
z-index: 1;
}
.icon-item:hover .icon-wrapper {
background: #1890ff;
color: white;
transform: scale(1.1);
}
.icon-name {
font-size: 12px;
color: #666;
text-align: center;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
word-break: break-all;
line-height: 1.3;
max-width: 100%;
position: relative;
z-index: 1;
}
.icon-item:hover .icon-name {
color: #1890ff;
font-weight: 500;
}
</style>
<template>
<div class="icons-size-demo">
<div class="size-showcase">
<div class="size-item" v-for="size in sizes" :key="size.label">
<div class="size-icon">
<TsIcon name="LoadingIcon" :fontSize="size.size" />
</div>
<div class="size-label">{{ size.label }}</div>
<div class="size-value">{{ size.size }}</div>
</div>
</div>
<div class="size-controls">
<div class="control-item">
<label>自定义大小:</label>
<TsSlider v-model="customSizeValue" :min="12" :max="64" :step="2" style="width: 200px" />
<div class="size-value-display">{{ customSizeValue }}px</div>
<div class="preview-icon">
<TsIcon name="SelectIcon" :fontSize="customSizeValue + 'px'" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsIcon, TsSlider } from 'tui';
const customSizeValue = ref(24);
const sizes = [
{ label: '超小', size: '12px' },
{ label: '小', size: '16px' },
{ label: '默认', size: '1em' },
{ label: '中等', size: '24px' },
{ label: '大', size: '32px' },
{ label: '超大', size: '48px' },
{ label: '巨大', size: '64px' },
];
</script>
<style scoped>
.icons-size-demo {
max-width: 800px;
}
.size-showcase {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 20px;
margin-bottom: 40px;
}
.size-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.size-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: #f8f9fa;
}
.size-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
border-radius: 6px;
margin-bottom: 8px;
transition: all 0.2s ease;
}
.size-label {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.size-item:hover .size-icon {
background: #e9ecef;
transform: scale(1.05);
}
.size-value {
font-size: 12px;
color: #666;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: rgba(24, 144, 255, 0.1);
padding: 2px 8px;
border-radius: 4px;
}
.size-controls {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.control-item {
display: flex;
align-items: center;
gap: 12px;
}
.control-item label {
font-size: 14px;
font-weight: 500;
color: #333;
min-width: 80px;
}
.size-value-display {
font-size: 14px;
font-weight: 600;
color: #333;
min-width: 50px;
text-align: center;
background: #f0f0f0;
padding: 4px 8px;
border-radius: 4px;
margin: 0 12px;
}
.preview-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: white;
border: 1px solid #d9d9d9;
border-radius: 8px;
}
</style>
<template>
<div class="icons-color-demo">
<div class="color-presets">
<div class="preset-title">预设颜色</div>
<div class="preset-grid">
<div v-for="color in presetColors" :key="color.name" class="preset-item">
<div class="preset-icon">
<TsIcon name="CheckCircleIcon" :style="{ color: color.value, fontSize: '32px' }" />
</div>
<div class="preset-info">
<div class="preset-name">{{ color.name }}</div>
<div class="preset-value">{{ color.value }}</div>
</div>
</div>
</div>
</div>
<div class="color-custom">
<div class="custom-title">自定义颜色</div>
<div class="custom-controls">
<div class="control-group">
<label>选择颜色:</label>
<input type="color" v-model="customColor" class="color-picker" />
<TsInput v-model="customColor" placeholder="#1890ff" style="width: 120px" />
</div>
<div class="preview-section">
<div class="preview-icons">
<div v-for="(icon, index) in previewIcons" :key="index" class="preview-item">
<TsIcon :name="icon as any" :style="{ color: customColor, fontSize: '24px' }" />
</div>
</div>
</div>
</div>
</div>
<div class="color-gradients">
<div class="gradient-title">渐变效果</div>
<div class="gradient-grid">
<div v-for="gradient in gradients" :key="gradient.name" class="gradient-item">
<div class="gradient-icon" :style="{ background: gradient.value, fontSize: '32px' }">
<TsIcon name="SelectIcon" style="color: white" />
</div>
<div class="gradient-info">
<div class="gradient-name">{{ gradient.name }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsIcon, TsInput } from 'tui';
const customColor = ref('#1890ff');
const presetColors = [
{ name: '主色', value: '#1890ff' },
{ name: '成功', value: '#52c41a' },
{ name: '警告', value: '#faad14' },
{ name: '错误', value: '#f5222d' },
{ name: '信息', value: '#722ed1' },
{ name: '灰色', value: '#666666' },
];
const previewIcons = ['CheckCircleIcon', 'SelectIcon', 'CheckCircleIcon', 'CloseIcon', 'ArrowDownIcon'];
const gradients = [
{ name: '蓝紫渐变', value: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
{ name: '橙红渐变', value: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' },
{ name: '青绿渐变', value: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' },
{ name: '金黄渐变', value: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' },
];
</script>
<style scoped>
.icons-color-demo {
max-width: 900px;
}
.color-presets {
margin-bottom: 40px;
}
.preset-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.preset-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.preset-item {
display: flex;
align-items: center;
padding: 12px 16px;
background: white;
border-radius: 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.preset-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: #f8f9fa;
}
.preset-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
border-radius: 6px;
margin-right: 12px;
transition: all 0.2s ease;
}
.preset-item:hover .preset-icon {
background: #e9ecef;
transform: scale(1.05);
}
.preset-info {
flex: 1;
}
.preset-name {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.preset-value {
font-size: 12px;
color: #666;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: #f5f5f5;
padding: 2px 8px;
border-radius: 4px;
display: inline-block;
}
.color-custom {
margin-bottom: 40px;
}
.custom-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.custom-controls {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.control-group {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
}
.control-group label {
font-size: 14px;
font-weight: 500;
color: #333;
min-width: 80px;
}
.color-picker {
width: 40px;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 6px;
cursor: pointer;
}
.preview-section {
display: flex;
justify-content: center;
}
.preview-icons {
display: flex;
gap: 12px;
padding: 16px;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.preview-item {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
border-radius: 6px;
transition: all 0.2s ease;
}
.preview-item:hover {
transform: scale(1.05);
background: #e9ecef;
}
.color-gradients {
margin-bottom: 40px;
}
.gradient-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.gradient-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
}
.gradient-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: white;
border-radius: 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.gradient-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: #f8f9fa;
}
.gradient-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
margin-bottom: 12px;
transition: all 0.2s ease;
}
.gradient-item:hover .gradient-icon {
transform: scale(1.05);
}
.gradient-name {
font-size: 12px;
color: #666;
text-align: center;
}
</style>
<template>
<div class="icons-animation-demo">
<div class="animation-section">
<div class="section-title">旋转动画</div>
<div class="animation-grid">
<div v-for="speed in rotationSpeeds" :key="speed.label" class="animation-item">
<div class="animation-icon">
<TsIcon name="LoadingIcon" :style="{ fontSize: '32px', animation: `rotate ${speed.duration} linear infinite` }" />
</div>
<div class="animation-label">{{ speed.label }}</div>
<div class="animation-value">{{ speed.duration }}</div>
</div>
</div>
</div>
<div class="animation-section">
<div class="section-title">脉冲动画</div>
<div class="animation-grid">
<div v-for="pulse in pulseTypes" :key="pulse.label" class="animation-item">
<div class="animation-icon">
<TsIcon :name="pulse.icon" :style="{ fontSize: '32px', animation: `${pulse.animation} ${pulse.duration} ease-in-out infinite` }" />
</div>
<div class="animation-label">{{ pulse.label }}</div>
<div class="animation-value">{{ pulse.duration }}</div>
</div>
</div>
</div>
<div class="animation-section">
<div class="section-title">弹跳动画</div>
<div class="animation-grid">
<div v-for="bounce in bounceTypes" :key="bounce.label" class="animation-item">
<div class="animation-icon">
<TsIcon :name="bounce.icon" :style="{ fontSize: '32px', animation: `${bounce.animation} ${bounce.duration} ease-in-out infinite` }" />
</div>
<div class="animation-label">{{ bounce.label }}</div>
<div class="animation-value">{{ bounce.duration }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { TsIcon } from 'tui';
const rotationSpeeds = [
{ label: '慢速', duration: '3s' },
{ label: '正常', duration: '2s' },
{ label: '快速', duration: '1s' },
{ label: '超快', duration: '0.5s' },
];
const pulseTypes = [
{ label: '心跳', icon: 'CheckCircleIcon' as any, animation: 'pulse', duration: '1.5s' },
{ label: '闪烁', icon: 'SelectIcon' as any, animation: 'blink', duration: '2s' },
{ label: '呼吸', icon: 'SearchIcon' as any, animation: 'breathe', duration: '3s' },
];
const bounceTypes = [
{ label: '弹跳', icon: 'ArrowExpandIcon' as any, animation: 'bounce', duration: '1s' },
{ label: '摇摆', icon: 'ErrorCircleIcon' as any, animation: 'shake', duration: '0.8s' },
{ label: '震动', icon: 'ExclamationCircleIcon' as any, animation: 'wiggle', duration: '0.3s' },
];
</script>
<style scoped>
.icons-animation-demo {
max-width: 800px;
}
.animation-section {
margin-bottom: 40px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 8px;
border-bottom: 2px solid #1890ff;
}
.animation-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 20px;
}
.animation-item {
display: flex;
align-items: center;
padding: 12px 16px;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.animation-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: #f8f9fa;
}
.animation-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
border-radius: 6px;
margin-right: 12px;
transition: all 0.2s ease;
position: relative;
}
.animation-item:hover .animation-icon {
background: #e9ecef;
transform: scale(1.05);
}
.animation-label {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.animation-value {
font-size: 12px;
color: #666;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: rgba(24, 144, 255, 0.1);
padding: 2px 8px;
border-radius: 4px;
}
/* 动画定义 */
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.8; }
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
@keyframes breathe {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(-3deg); }
75% { transform: rotate(3deg); }
}
</style>
<template>
<div class="icons-interactive-demo">
<div class="interactive-section">
<div class="section-title">点击交互</div>
<div class="click-demo">
<div class="click-item" v-for="item in clickItems" :key="item.id" @click="handleClick(item)">
<div class="click-icon" :class="{ 'is-active': item.active }">
<TsIcon :name="item.icon as any" :style="{ fontSize: '24px' }" />
</div>
<div class="click-label">{{ item.label }}</div>
<div class="click-count">{{ item.count }} 次</div>
</div>
</div>
</div>
<div class="interactive-section">
<div class="section-title">悬停效果</div>
<div class="hover-demo">
<div v-for="effect in hoverEffects" :key="effect.name" class="hover-item" :class="`hover-${effect.name}`">
<div class="hover-icon">
<TsIcon :name="effect.icon" :style="{ fontSize: '32px' }" />
</div>
<div class="hover-label">{{ effect.label }}</div>
</div>
</div>
</div>
<div class="interactive-section">
<div class="section-title">状态切换</div>
<div class="toggle-demo">
<div class="toggle-item" v-for="toggle in toggles" :key="toggle.id">
<div class="toggle-icon" :class="{ 'is-toggled': toggle.state }" @click="toggleState(toggle)">
<TsIcon :name="(toggle.state ? toggle.activeIcon : toggle.inactiveIcon) as any" :style="{ fontSize: '28px' }" />
</div>
<div class="toggle-label">{{ toggle.label }}</div>
<div class="toggle-status">{{ toggle.state ? '开启' : '关闭' }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { TsIcon, TsToast } from 'tui';
interface ClickItem {
id: number;
icon: string;
label: string;
count: number;
active: boolean;
}
interface ToggleItem {
id: number;
label: string;
inactiveIcon: string;
activeIcon: string;
state: boolean;
}
const clickItems = ref<ClickItem[]>([
{ id: 1, icon: 'CheckCircleIcon', label: '喜欢', count: 0, active: false },
{ id: 2, icon: 'SelectIcon', label: '收藏', count: 0, active: false },
{ id: 3, icon: 'CheckCircleIcon', label: '完成', count: 0, active: false },
{ id: 4, icon: 'ArrowRightIcon', label: '分享', count: 0, active: false },
]);
const toggles = ref<ToggleItem[]>([
{ id: 1, label: '静音', inactiveIcon: 'SearchIcon', activeIcon: 'ErrorCircleIcon', state: false },
{ id: 2, label: '播放', inactiveIcon: 'ArrowRightIcon', activeIcon: 'ArrowDownIcon', state: false },
{ id: 3, label: '显示', inactiveIcon: 'PreviewIcon', activeIcon: 'CloseIcon', state: true },
{ id: 4, label: '锁定', inactiveIcon: 'ArrowExpandIcon', activeIcon: 'ArrowHideIcon', state: false },
]);
const hoverEffects = [
{ name: 'scale', icon: 'ArrowExpandIcon' as any, label: '缩放' },
{ name: 'rotate', icon: 'RotateLeftIcon' as any, label: '旋转' },
{ name: 'color', icon: 'SearchIcon' as any, label: '变色' },
{ name: 'shadow', icon: 'SunIcon' as any, label: '阴影' },
];
const handleClick = (item: ClickItem) => {
item.count++;
item.active = !item.active;
TsToast({
type: 'info',
message: `${item.label} ${item.active ? '激活' : '取消'},点击 ${item.count} 次`,
useSingleton: false,
duration: 2000
});
};
const toggleState = (toggle: ToggleItem) => {
toggle.state = !toggle.state;
};
</script>
<style scoped>
.icons-interactive-demo {
max-width: 900px;
}
.interactive-section {
margin-bottom: 40px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
padding-bottom: 8px;
border-bottom: 2px solid #1890ff;
}
.click-demo {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 16px;
}
.click-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: white;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.click-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background: #f8f9fa;
}
.click-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
border-radius: 6px;
margin-bottom: 8px;
transition: all 0.2s ease;
}
.click-icon.is-active {
background: #1890ff;
color: white;
transform: scale(1.05);
}
.click-label {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.click-count {
font-size: 12px;
color: #666;
background: #f5f5f5;
padding: 2px 8px;
border-radius: 12px;
}
.hover-demo {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 20px;
}
.hover-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: white;
border-radius: 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
cursor: pointer;
}
.hover-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
border-radius: 6px;
margin-bottom: 8px;
transition: all 0.2s ease;
}
.hover-label {
font-size: 14px;
font-weight: 500;
color: #666;
}
/* 悬停效果 */
.hover-scale:hover .hover-icon {
transform: scale(1.2);
background: #1890ff;
color: white;
}
.hover-rotate:hover .hover-icon {
transform: rotate(180deg);
background: #e9ecef;
}
.hover-color:hover .hover-icon {
background: #1890ff;
color: white;
transform: scale(1.1);
}
.hover-shadow:hover .hover-icon {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
background: #faad14;
color: white;
transform: translateY(-2px);
}
.toggle-demo {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 20px;
}
.toggle-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 24px;
background: white;
border: 2px solid #e8e8e8;
border-radius: 12px;
transition: all 0.3s ease;
}
.toggle-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
border-radius: 6px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.toggle-icon:hover {
transform: scale(1.05);
}
.toggle-icon.is-toggled {
background: #52c41a;
color: white;
border-color: #52c41a;
}
.toggle-label {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.toggle-status {
font-size: 12px;
color: #666;
background: #f5f5f5;
padding: 2px 8px;
border-radius: 4px;
}
.toggle-icon.is-toggled + .toggle-label + .toggle-status {
background: #f6ffed;
color: #52c41a;
}
</style>