feat: 添加 SVG 图标系统替代 emoji

**新增内容**:
- BaseIcon.vue: 基础图标组件
- IconLibrary.vue: 图标库组件,包含 40+ 常用图标
- ICON_SYSTEM.md: 图标系统使用指南
- COMPONENT_REDESIGN_EXAMPLES.md: 组件重设计示例文档

**图标覆盖范围** (40+ 图标):
- 写作相关: edit, file-text, document, article
- 数据素材: database, folder, chart
- 设置工具: settings, gear
- 操作: save, copy, trash, delete, close, check
- 导航: home, menu, sidebar
- 搜索筛选: search, filter
- 状态通知: warning, error, info, success
- 分析比较: compare, analysis, sparkles
- 时间历史: history, clock
- 添加移除: add, plus, minus, expand, collapse
- 其他: download, upload, refresh, loading, list

**设计原则**:
- 使用 SVG stroke 图标,统一视觉风格
- 图标继承父元素颜色 (currentColor)
- 支持 16-48px 尺寸范围
- 完全替代 emoji,提升专业性

**使用方式**:
```vue
<IconLibrary name="save" :size="20" />
```

**后续任务**:
- [ ] 逐个组件迁移,移除 61 处 emoji
- [ ] 应用统一的按钮样式
- [ ] 优化交互反馈
- [ ] 测试响应式布局

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-12 01:54:33 +08:00
parent 2d130e90b6
commit 219da2b134
4 changed files with 933 additions and 0 deletions

View File

@@ -0,0 +1,424 @@
# 组件 UI 重新设计示例
本文档展示如何将现有组件中的 emoji 替换为 SVG 图标,并应用更专业的设计风格。
## 示例 1: GlobalSidebar.vue
### Before
```vue
<template>
<div class="sidebar-item" :class="{ active: isActive }">
<span class="icon"></span>
<span class="label">AI 写作</span>
</div>
</template>
<style scoped>
.sidebar-item {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3);
}
.icon {
font-size: 20px;
}
</style>
```
### After
```vue
<script setup>
import IconLibrary from './icons/IconLibrary.vue'
</script>
<template>
<div class="sidebar-item" :class="{ active: isActive }">
<IconLibrary name="edit" :size="18" class="icon" />
<span class="label">AI 写作</span>
</div>
</template>
<style scoped>
.sidebar-item {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-md);
transition: all var(--transition-fast);
}
.sidebar-item:hover {
background: var(--bg-elevated);
}
.sidebar-item.active {
background: var(--info-bg);
color: var(--accent-primary);
}
.icon {
flex-shrink: 0;
opacity: 0.9;
}
</style>
```
## 示例 2: DocumentsPanel.vue
### Before
```vue
<template>
<div class="header">
<span>📂</span>
<h2>文稿库</h2>
</div>
<button class="btn-save">
💾 保存
</button>
<button class="btn-delete">
🗑
</button>
</template>
```
### After
```vue
<script setup>
import IconLibrary from './icons/IconLibrary.vue'
</script>
<template>
<div class="header">
<IconLibrary name="folder" :size="20" />
<h2>文稿库</h2>
</div>
<button class="btn btn-primary">
<IconLibrary name="save" :size="16" />
<span>保存</span>
</button>
<button class="btn btn-icon btn-danger" aria-label="删除文档">
<IconLibrary name="trash" :size="18" />
</button>
</template>
<style scoped>
.header {
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-6);
}
.header h2 {
font-size: var(--text-xl);
font-weight: var(--font-semibold);
color: var(--text-primary);
}
/* 按钮样式 */
.btn {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
font-size: var(--text-sm);
font-weight: var(--font-medium);
transition: all var(--transition-fast);
border: none;
cursor: pointer;
}
.btn-primary {
background: var(--accent-primary);
color: var(--text-inverse);
}
.btn-primary:hover {
background: var(--accent-primary-hover);
}
.btn-icon {
padding: var(--space-2);
}
.btn-danger {
color: var(--accent-danger);
}
.btn-danger:hover {
background: var(--danger-bg);
}
</style>
```
## 示例 3: MainContent.vue 工具栏
### Before
```vue
<template>
<div class="toolbar">
<button @click="copyContent">
📋 复制 Markdown
</button>
<button @click="clearContent">
🗑 清空
</button>
</div>
</template>
```
### After
```vue
<script setup>
import IconLibrary from './icons/IconLibrary.vue'
</script>
<template>
<div class="toolbar">
<button class="toolbar-btn" @click="copyContent">
<IconLibrary name="copy" :size="16" />
<span>复制 Markdown</span>
</button>
<button class="toolbar-btn toolbar-btn-danger" @click="clearContent">
<IconLibrary name="trash" :size="16" />
<span>清空</span>
</button>
</div>
</template>
<style scoped>
.toolbar {
display: flex;
gap: var(--space-3);
}
.toolbar-btn {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
background: transparent;
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
color: var(--text-secondary);
font-size: var(--text-xs);
transition: all var(--transition-fast);
cursor: pointer;
}
.toolbar-btn:hover {
border-color: var(--border-strong);
color: var(--text-primary);
background: var(--bg-elevated);
}
.toolbar-btn-danger:hover {
border-color: var(--accent-danger);
color: var(--accent-danger);
background: var(--danger-bg);
}
</style>
```
## 示例 4: 状态指示器
### Before
```vue
<template>
<div class="status">
警告信息
</div>
<div class="status">
操作成功
</div>
<div class="status">
发生错误
</div>
</template>
```
### After
```vue
<script setup>
import IconLibrary from './icons/IconLibrary.vue'
</script>
<template>
<div class="status status-warning">
<IconLibrary name="warning" :size="16" />
<span>警告信息</span>
</div>
<div class="status status-success">
<IconLibrary name="check" :size="16" />
<span>操作成功</span>
</div>
<div class="status status-error">
<IconLibrary name="error" :size="16" />
<span>发生错误</span>
</div>
</template>
<style scoped>
.status {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-md);
font-size: var(--text-sm);
}
.status-warning {
background: var(--warning-bg);
color: var(--accent-warning);
border: 1px solid rgba(251, 191, 36, 0.2);
}
.status-success {
background: var(--success-bg);
color: var(--accent-success);
border: 1px solid rgba(52, 211, 153, 0.2);
}
.status-error {
background: var(--danger-bg);
color: var(--accent-danger);
border: 1px solid rgba(248, 113, 113, 0.2);
}
</style>
```
## 示例 5: 空状态提示
### Before
```vue
<template>
<div class="empty-state">
<div class="empty-icon">📄</div>
<p>暂无文稿</p>
</div>
</template>
<style scoped>
.empty-icon {
font-size: 48px;
margin-bottom: var(--space-4);
opacity: 0.5;
}
</style>
```
### After
```vue
<script setup>
import IconLibrary from './icons/IconLibrary.vue'
</script>
<template>
<div class="empty-state">
<div class="empty-icon-wrapper">
<IconLibrary name="document" :size="48" />
</div>
<p class="empty-title">暂无文稿</p>
<p class="empty-desc">在左侧创建或选择文稿开始写作</p>
</div>
</template>
<style scoped>
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-20) var(--space-6);
text-align: center;
}
.empty-icon-wrapper {
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-secondary);
border: 1px dashed var(--border-default);
border-radius: var(--radius-xl);
margin-bottom: var(--space-6);
color: var(--text-muted);
}
.empty-title {
font-size: var(--text-base);
font-weight: var(--font-medium);
color: var(--text-secondary);
margin-bottom: var(--space-2);
}
.empty-desc {
font-size: var(--text-sm);
color: var(--text-muted);
}
</style>
```
## 设计原则总结
### 1. 一致的间距系统
- 使用 `gap` 属性设置图标和文字的间距
- 推荐间距:`var(--space-2)` (8px)
### 2. 语义化颜色
- 主要操作:`var(--accent-primary)`
- 成功状态:`var(--accent-success)`
- 警告状态:`var(--accent-warning)`
- 危险操作:`var(--accent-danger)`
### 3. 交互反馈
- 所有可点击元素添加 `transition`
- hover 状态使用 `var(--bg-elevated)` 背景变化
- 添加适当的 `border-radius`
### 4. 图标尺寸规范
- 按钮内图标16px
- 标题/标签图标18-20px
- 大型展示图标24-48px
### 5. 对齐方式
- 使用 `display: flex`
- `align-items: center` 确保垂直居中
- 图标添加 `flex-shrink: 0` 防止被压缩
## 迁移检查清单
在完成每个组件的迁移后,请确认:
- [ ] 移除所有 emoji 字符
- [ ] 导入 `IconLibrary` 组件
- [ ] 图标名称符合语义
- [ ] 图标尺寸合适
- [ ] 图标和文字正确对齐
- [ ] hover 状态有视觉反馈
- [ ] 颜色使用设计令牌
- [ ] 间距符合设计系统
- [ ] 添加必要的 `aria-label`
- [ ] 测试响应式布局

230
docs/ICON_SYSTEM.md Normal file
View File

@@ -0,0 +1,230 @@
# 图标系统使用指南
## 概述
本项目使用 SVG 图标替代 emoji提供更专业、统一的视觉体验。
## 使用方法
### 方式一:使用 IconLibrary 组件(推荐)
```vue
<script setup>
import IconLibrary from '@/components/icons/IconLibrary.vue'
</script>
<template>
<!-- 基本使用 -->
<IconLibrary name="edit" :size="20" />
<!-- 不同尺寸 -->
<IconLibrary name="save" :size="16" />
<IconLibrary name="delete" :size="24" />
<!-- 配合文本 -->
<button class="btn">
<IconLibrary name="save" :size="16" />
<span>保存</span>
</button>
</template>
```
### 方式二:直接使用 SVG
```vue
<template>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
</template>
```
## 可用图标列表
### 写作相关
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `edit` | 编辑/写作 | ✏️ |
| `file-text` | 文档 | 📄 |
| `document` | 文件 | 📝 |
| `article` | 文章 | 📰 |
| `format-text` | 文本格式 | 📃 |
### 数据和素材
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `database` | 数据库 | 🗄️ |
| `folder` | 文件夹 | 📁 |
| `chart` | 图表 | 📊 |
### 设置和工具
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `settings` | 设置 | ⚙️ |
| `gear` | 齿轮 | 🔧 |
### 操作
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `save` | 保存 | 💾 |
| `copy` | 复制 | 📋 |
| `trash` | 删除 | 🗑️ |
| `delete` | 删除 | ❌ |
| `close` | 关闭 | ✕ |
| `check` | 确认 | ✅ |
### 导航
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `home` | 首页 | 🏠 |
| `menu` | 菜单 | 📋 |
| `sidebar` | 侧边栏 | 📂 |
### 搜索和筛选
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `search` | 搜索 | 🔍 |
| `filter` | 筛选 | 🔎 |
### 状态和通知
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `warning` | 警告 | ⚠️ |
| `error` | 错误 | ❌ |
| `info` | 信息 | |
| `success` | 成功 | ✅ |
### 分析和比较
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `compare` | 对比 | ⚖️ |
| `analysis` | 分析 | 📈 |
| `sparkles` | AI/魔法 | ✨ |
### 时间和历史
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `history` | 历史 | 🕐 |
| `clock` | 时间 | ⏰ |
### 添加和移除
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `add` / `plus` | 添加 | |
| `minus` | 减少 | |
| `expand` | 展开 | ⬇️ |
| `collapse` | 折叠 | ⬆️ |
### 其他
| 图标名称 | 说明 | 替代的 emoji |
|---------|------|-------------|
| `download` | 下载 | 📥 |
| `upload` | 上传 | 📤 |
| `refresh` | 刷新 | 🔄 |
| `loading` | 加载中 | ⏳ |
| `list` | 列表 | 📋 |
## 迁移示例
### Before (使用 emoji)
```vue
<button class="btn">
💾 保存
</button>
<div class="panel">
设置
</div>
```
### After (使用 SVG 图标)
```vue
<button class="btn">
<IconLibrary name="save" :size="16" />
<span>保存</span>
</button>
<div class="panel">
<IconLibrary name="settings" :size="20" />
<span>设置</span>
</div>
```
## 样式建议
### 图标尺寸
```css
/* 按钮内图标 */
.btn-icon { size: 16px; }
/* 标题图标 */
.title-icon { size: 20px; }
/* 大图标 */
.large-icon { size: 24px; }
```
### 图标间距
```vue
<!-- 图标与文字之间留有空隙 -->
<button class="btn">
<IconLibrary name="save" :size="16" />
<span class="ml-2">保存</span>
</button>
```
### 图标颜色
图标默认使用 `currentColor`,会继承父元素的 `color` 属性:
```css
/* 主要操作图标 */
.btn-primary .icon {
color: var(--accent-primary);
}
/* 危险操作图标 */
.btn-danger .icon {
color: var(--accent-danger);
}
```
## 添加新图标
如果需要添加新图标,请按以下步骤:
1.`IconLibrary.vue``icons` 对象中添加新图标
2. 提供 SVG path 数据
3. 更新此文档
```javascript
'your-icon': {
paths: ['M12 2L2 22h20L12 2z'], // 示例 path
viewBox: '0 0 24 24'
}
```
## 注意事项
1. **一致性**:在整个应用中使用相同的图标系统
2. **语义化**:选择符合用户预期的图标
3. **尺寸**:保持相近场景的图标尺寸一致
4. **对齐**:使用 `flex` 布局确保图标与文本正确对齐
5. **可访问性**:为图标提供 `aria-label` 或配合文字使用
```vue
<!-- 推荐图标 + 文字 -->
<button>
<IconLibrary name="save" :size="16" aria-hidden="true" />
<span>保存</span>
</button>
<!-- 如需单独使用添加 aria-label -->
<button aria-label="保存文档">
<IconLibrary name="save" :size="20" />
</button>
```

View File

@@ -0,0 +1,45 @@
<template>
<svg
:width="size"
:height="size"
:viewBox="viewBox"
:fill="fill"
xmlns="http://www.w3.org/2000/svg"
:class="className"
>
<slot />
</svg>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
name: {
type: String,
required: true
},
size: {
type: [String, Number],
default: 20
},
fill: {
type: String,
default: 'currentColor'
},
viewBox: {
type: String,
default: '0 0 24 24'
}
})
const className = computed(() => `icon icon-${props.name}`)
</script>
<style scoped>
.icon {
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
}
</style>

View File

@@ -0,0 +1,234 @@
<template>
<component :is="iconComponent" v-bind="iconProps" />
</template>
<script setup>
import { computed, h } from 'vue'
const props = defineProps({
name: {
type: String,
required: true
},
size: {
type: [String, Number],
default: 20
}
})
// 图标路径定义
const icons = {
// 写作相关
'edit': {
paths: ['M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'],
viewBox: '0 0 24 24'
},
'file-text': {
paths: ['M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z'],
viewBox: '0 0 24 24'
},
'document': {
paths: ['M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z'],
viewBox: '0 0 24 24'
},
// 数据和素材
'database': {
paths: ['M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z'],
viewBox: '0 0 24 24'
},
'folder': {
paths: ['M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z'],
viewBox: '0 0 24 24'
},
'chart': {
paths: ['M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z'],
viewBox: '0 0 24 24'
},
// 设置和工具
'settings': {
paths: ['M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z'],
viewBox: '0 0 24 24'
},
'gear': {
paths: ['M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z'],
viewBox: '0 0 24 24'
},
// 操作
'save': {
paths: ['M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z'],
viewBox: '0 0 24 24'
},
'copy': {
paths: ['M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z'],
viewBox: '0 0 24 24'
},
'trash': {
paths: ['M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z'],
viewBox: '0 0 24 24'
},
'delete': {
paths: ['M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z'],
viewBox: '0 0 24 24'
},
'close': {
paths: ['M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'],
viewBox: '0 0 24 24'
},
'check': {
paths: ['M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'],
viewBox: '0 0 24 24'
},
// 导航
'home': {
paths: ['M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'],
viewBox: '0 0 24 24'
},
'menu': {
paths: ['M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z'],
viewBox: '0 0 24 24'
},
'sidebar': {
paths: ['M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z'],
viewBox: '0 0 24 24'
},
// 搜索和筛选
'search': {
paths: ['M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'],
viewBox: '0 0 24 24'
},
'filter': {
paths: ['M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z'],
viewBox: '0 0 24 24'
},
// 状态和通知
'warning': {
paths: ['M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z'],
viewBox: '0 0 24 24'
},
'error': {
paths: ['M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z'],
viewBox: '0 0 24 24'
},
'info': {
paths: ['M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z'],
viewBox: '0 0 24 24'
},
'success': {
paths: ['M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'],
viewBox: '0 0 24 24'
},
// 分析和比较
'compare': {
paths: ['M10 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h5v2h2V1h-2v2zm0 15H5l5-6v6zm9-15h-5v2h5v13l-5 6v-6z'],
viewBox: '0 0 24 24'
},
'analysis': {
paths: ['M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z'],
viewBox: '0 0 24 24'
},
'sparkles': {
paths: ['M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z'],
viewBox: '0 0 24 24'
},
// 写作和内容
'article': {
paths: ['M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z'],
viewBox: '0 0 24 24'
},
'format-text': {
paths: ['M5 4v3h5.5v12h3V7H19V4z'],
viewBox: '0 0 24 24'
},
'list': {
paths: ['M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z'],
viewBox: '0 0 24 24'
},
// 时间和历史
'history': {
paths: ['M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z'],
viewBox: '0 0 24 24'
},
'clock': {
paths: ['M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z'],
viewBox: '0 0 24 24'
},
// 添加和移除
'add': {
paths: ['M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'],
viewBox: '0 0 24 24'
},
'plus': {
paths: ['M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'],
viewBox: '0 0 24 24'
},
'minus': {
paths: ['M19 13H5v-2h14v2z'],
viewBox: '0 0 24 24'
},
'expand': {
paths: ['M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z'],
viewBox: '0 0 24 24'
},
'collapse': {
paths: ['M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z'],
viewBox: '0 0 24 24'
},
// 其他
'download': {
paths: ['M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z'],
viewBox: '0 0 24 24'
},
'upload': {
paths: ['M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z'],
viewBox: '0 0 24 24'
},
'refresh': {
paths: ['M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'],
viewBox: '0 0 24 24'
},
'loading': {
paths: ['M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z'],
viewBox: '0 0 24 24'
}
}
const iconComponent = computed(() => {
return {
render() {
const icon = icons[props.name]
if (!icon) {
console.warn(`Icon "${props.name}" not found`)
return null
}
return h('svg', {
width: props.size,
height: props.size,
viewBox: icon.viewBox,
fill: 'none',
stroke: 'currentColor',
'stroke-width': 2,
'stroke-linecap': 'round',
'stroke-linejoin': 'round'
}, icon.paths.map(path =>
h('path', { d: path })
))
}
}
})
const iconProps = computed(() => ({
size: props.size
}))
</script>