Files
ai-write/docs/COMPONENT_REDESIGN_EXAMPLES.md
empty 219da2b134 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>
2026-01-12 01:54:33 +08:00

7.9 KiB
Raw Permalink Blame History

组件 UI 重新设计示例

本文档展示如何将现有组件中的 emoji 替换为 SVG 图标,并应用更专业的设计风格。

示例 1: GlobalSidebar.vue

Before

<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

<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

<template>
  <div class="header">
    <span>📂</span>
    <h2>文稿库</h2>
  </div>

  <button class="btn-save">
    💾 保存
  </button>

  <button class="btn-delete">
    🗑️
  </button>
</template>

After

<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

<template>
  <div class="toolbar">
    <button @click="copyContent">
      📋 复制 Markdown
    </button>
    <button @click="clearContent">
      🗑 清空
    </button>
  </div>
</template>

After

<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

<template>
  <div class="status">
    ⚠️ 警告信息
  </div>
  <div class="status">
     操作成功
  </div>
  <div class="status">
     发生错误
  </div>
</template>

After

<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

<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

<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
  • 测试响应式布局