- 完整实现Apple HIG 2025设计系统 - 添加Modal弹窗组件(毛玻璃效果、流畅动画) - 添加FileUpload文件上传组件(拖拽、批量上传、进度显示) - 实现产品上传功能(支持DWG/GLB/GLTF文件) - 优化产品列表展示(可折叠的产品+SKU结构) - 实现Tab切换功能(3D模型管理、五金件、BOM、文档) - 修复主题系统hydration错误 - 优化按钮和间距,符合Apple设计规范 - 添加分享页面的Apple风格重设计 - 创建测试数据脚本
624 lines
14 KiB
Markdown
624 lines
14 KiB
Markdown
# Apple 风格 UI 组件使用指南
|
||
|
||
本指南提供了 Apple 风格 UI 设计系统中所有组件的详细使用说明和最佳实践。
|
||
|
||
## 目录
|
||
|
||
1. [设计令牌](#设计令牌)
|
||
2. [基础组件](#基础组件)
|
||
3. [导航组件](#导航组件)
|
||
4. [数据展示组件](#数据展示组件)
|
||
5. [反馈组件](#反馈组件)
|
||
6. [媒体组件](#媒体组件)
|
||
7. [布局组件](#布局组件)
|
||
8. [动画组件](#动画组件)
|
||
9. [可访问性工具](#可访问性工具)
|
||
10. [性能优化](#性能优化)
|
||
|
||
## 设计令牌
|
||
|
||
### 字体系统
|
||
|
||
```typescript
|
||
import { LargeTitle, Title1, Body, Caption1 } from '@/components/ui/Typography'
|
||
|
||
function MyComponent() {
|
||
return (
|
||
<div>
|
||
<LargeTitle>大标题 (34pt)</LargeTitle>
|
||
<Title1>标题 1 (28pt)</Title1>
|
||
<Body>正文内容 (17pt)</Body>
|
||
<Caption1>说明文字 (12pt)</Caption1>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
**最佳实践:**
|
||
- 使用语义化的字体组件而不是直接的 CSS 类
|
||
- 遵循苹果的字体层级,不要跳级使用
|
||
- 中英文混排时使用 `fontType="chinese"` 属性
|
||
|
||
### 颜色系统
|
||
|
||
```typescript
|
||
// 使用 Tailwind 类名
|
||
<div className="bg-apple-blue-light text-white">
|
||
蓝色背景
|
||
</div>
|
||
|
||
// 使用设计令牌
|
||
import { appleColorTokens } from '@/lib/design-tokens'
|
||
const blueColor = appleColorTokens.system.blue.light
|
||
```
|
||
|
||
**最佳实践:**
|
||
- 优先使用语义化颜色(如 `apple-bg-primary`)
|
||
- 深色模式会自动切换颜色
|
||
- 使用系统颜色保持一致性
|
||
|
||
## 基础组件
|
||
|
||
### Button 按钮
|
||
|
||
```typescript
|
||
import Button from '@/components/ui/Button'
|
||
|
||
function ButtonExample() {
|
||
return (
|
||
<div className="space-x-4">
|
||
{/* 四种变体 */}
|
||
<Button variant="filled">Filled</Button>
|
||
<Button variant="tinted">Tinted</Button>
|
||
<Button variant="gray">Gray</Button>
|
||
<Button variant="plain">Plain</Button>
|
||
|
||
{/* 三种尺寸 */}
|
||
<Button size="small">Small (32px)</Button>
|
||
<Button size="regular">Regular (44px)</Button>
|
||
<Button size="large">Large (50px)</Button>
|
||
|
||
{/* 状态 */}
|
||
<Button loading>Loading</Button>
|
||
<Button disabled>Disabled</Button>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
**最佳实践:**
|
||
- 主要操作使用 `filled` 变体
|
||
- 次要操作使用 `tinted` 或 `gray` 变体
|
||
- 确保按钮最小尺寸为 44px(苹果触摸目标标准)
|
||
- 使用 `loading` 状态而不是禁用按钮
|
||
|
||
### Input 输入框
|
||
|
||
```typescript
|
||
import Input from '@/components/ui/Input'
|
||
|
||
function InputExample() {
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* 基础输入框 */}
|
||
<Input
|
||
label="用户名"
|
||
placeholder="请输入用户名"
|
||
helperText="用户名必须唯一"
|
||
required
|
||
/>
|
||
|
||
{/* 状态变体 */}
|
||
<Input
|
||
label="邮箱"
|
||
type="email"
|
||
state="success"
|
||
helperText="邮箱格式正确"
|
||
/>
|
||
|
||
<Input
|
||
label="密码"
|
||
type="password"
|
||
errorText="密码长度至少 8 位"
|
||
/>
|
||
|
||
{/* 带图标 */}
|
||
<Input
|
||
label="搜索"
|
||
leftIcon="🔍"
|
||
rightIcon="✕"
|
||
onRightIconClick={() => console.log('清除')}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
**最佳实践:**
|
||
- 始终提供 `label` 属性以支持可访问性
|
||
- 使用 `helperText` 提供额外说明
|
||
- 错误状态使用 `errorText` 而不是 `helperText`
|
||
- 合理使用图标增强用户体验
|
||
|
||
### Card 卡片
|
||
|
||
```typescript
|
||
import Card, { CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/Card'
|
||
|
||
function CardExample() {
|
||
return (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{/* 简单卡片 */}
|
||
<Card className="p-6">
|
||
<h3 className="text-lg font-semibold mb-2">卡片标题</h3>
|
||
<p className="text-gray-600">卡片内容</p>
|
||
</Card>
|
||
|
||
{/* 组合卡片 */}
|
||
<Card variant="elevated">
|
||
<CardHeader>
|
||
<CardTitle>高级卡片</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p>使用组合组件构建的卡片</p>
|
||
</CardContent>
|
||
<CardFooter>
|
||
<Button size="small">操作</Button>
|
||
</CardFooter>
|
||
</Card>
|
||
|
||
{/* 交互式卡片 */}
|
||
<Card
|
||
interactive
|
||
onClick={() => console.log('卡片点击')}
|
||
className="cursor-pointer"
|
||
>
|
||
<div className="p-6">
|
||
<h3>可点击卡片</h3>
|
||
<p>支持 hover 和 active 动画</p>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
**最佳实践:**
|
||
- 使用 `variant="elevated"` 创建层次感
|
||
- 交互式卡片添加 `interactive` 属性
|
||
- 保持卡片内容简洁,避免过度嵌套
|
||
|
||
## 导航组件
|
||
|
||
### NavigationBar 导航栏
|
||
|
||
```typescript
|
||
import NavigationBar, { BackButton, MenuButton } from '@/components/ui/NavigationBar'
|
||
|
||
function NavigationExample() {
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* 大标题模式 */}
|
||
<NavigationBar
|
||
mode="large"
|
||
title="大标题导航"
|
||
subtitle="副标题说明"
|
||
leftContent={<BackButton onBack={() => history.back()} />}
|
||
rightContent={<MenuButton onMenu={() => console.log('菜单')} />}
|
||
/>
|
||
|
||
{/* 标准模式 */}
|
||
<NavigationBar
|
||
mode="standard"
|
||
title="标准导航"
|
||
leftContent={<BackButton />}
|
||
rightContent={
|
||
<div className="flex gap-2">
|
||
<Button size="small" variant="plain">编辑</Button>
|
||
<Button size="small" variant="tinted">保存</Button>
|
||
</div>
|
||
}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### TabBar 标签栏
|
||
|
||
```typescript
|
||
import TabBar, { useTabBar } from '@/components/ui/TabBar'
|
||
|
||
function TabBarExample() {
|
||
const tabBar = useTabBar('home')
|
||
|
||
const tabs = [
|
||
{ id: 'home', label: '首页', icon: '🏠' },
|
||
{ id: 'search', label: '搜索', icon: '🔍' },
|
||
{ id: 'favorites', label: '收藏', icon: '❤️', badge: 3 },
|
||
{ id: 'profile', label: '我的', icon: '👤' },
|
||
]
|
||
|
||
return (
|
||
<TabBar
|
||
items={tabs}
|
||
activeId={tabBar.activeTab}
|
||
onTabChange={tabBar.switchTab}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
**最佳实践:**
|
||
- 标签栏最多显示 5 个标签
|
||
- 使用 `useTabBar` Hook 管理状态
|
||
- 为重要标签添加徽章提示
|
||
|
||
## 数据展示组件
|
||
|
||
### Badge 徽章
|
||
|
||
```typescript
|
||
import Badge, { Tag, NumberBadge, StatusDot } from '@/components/ui/Badge'
|
||
|
||
function BadgeExample() {
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* 基础徽章 */}
|
||
<div className="flex gap-2">
|
||
<Badge>Default</Badge>
|
||
<Badge type="success">Success</Badge>
|
||
<Badge type="error">Error</Badge>
|
||
<Badge type="warning">Warning</Badge>
|
||
<Badge type="info">Info</Badge>
|
||
</div>
|
||
|
||
{/* 数字徽章 */}
|
||
<div className="flex gap-2">
|
||
<NumberBadge count={5} />
|
||
<NumberBadge count={99} max={99} />
|
||
<NumberBadge count={999} max={99} />
|
||
</div>
|
||
|
||
{/* 标签 */}
|
||
<div className="flex gap-2">
|
||
<Tag closable onClose={() => console.log('关闭')}>
|
||
可关闭标签
|
||
</Tag>
|
||
<Tag icon="🔥" type="success">
|
||
带图标标签
|
||
</Tag>
|
||
</div>
|
||
|
||
{/* 状态点 */}
|
||
<div className="flex gap-2">
|
||
<StatusDot status="online" />
|
||
<StatusDot status="busy" />
|
||
<StatusDot status="away" />
|
||
<StatusDot status="offline" />
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### ProgressBar 进度条
|
||
|
||
```typescript
|
||
import ProgressBar, { StepProgress } from '@/components/ui/ProgressBar'
|
||
|
||
function ProgressExample() {
|
||
const [progress, setProgress] = useState(45)
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* 线性进度条 */}
|
||
<ProgressBar
|
||
value={progress}
|
||
showLabel
|
||
showPercentage
|
||
label="下载进度"
|
||
animated
|
||
/>
|
||
|
||
{/* 环形进度条 */}
|
||
<ProgressBar
|
||
value={progress}
|
||
variant="circular"
|
||
size="large"
|
||
type="success"
|
||
showPercentage
|
||
/>
|
||
|
||
{/* 步骤进度 */}
|
||
<StepProgress
|
||
current={2}
|
||
total={4}
|
||
showLabels
|
||
labels={['开始', '配置', '确认', '完成']}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
## 反馈组件
|
||
|
||
### Modal 模态框
|
||
|
||
```typescript
|
||
import Modal, { ConfirmModal } from '@/components/ui/Modal'
|
||
|
||
function ModalExample() {
|
||
const [showModal, setShowModal] = useState(false)
|
||
const [showConfirm, setShowConfirm] = useState(false)
|
||
|
||
return (
|
||
<div>
|
||
<Button onClick={() => setShowModal(true)}>
|
||
打开模态框
|
||
</Button>
|
||
|
||
<Modal
|
||
open={showModal}
|
||
onClose={() => setShowModal(false)}
|
||
title="模态框标题"
|
||
description="模态框描述"
|
||
size="medium"
|
||
>
|
||
<div className="space-y-4">
|
||
<p>模态框内容</p>
|
||
<div className="flex gap-3">
|
||
<Button variant="filled">确认</Button>
|
||
<Button variant="gray" onClick={() => setShowModal(false)}>
|
||
取消
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
|
||
<ConfirmModal
|
||
open={showConfirm}
|
||
onClose={() => setShowConfirm(false)}
|
||
title="确认删除"
|
||
description="此操作无法撤销"
|
||
onConfirm={() => console.log('确认删除')}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Toast 提示
|
||
|
||
```typescript
|
||
import { useToast, ToastContainer } from '@/components/ui/Toast'
|
||
|
||
function ToastExample() {
|
||
const toast = useToast()
|
||
|
||
return (
|
||
<div>
|
||
<div className="space-x-2">
|
||
<Button onClick={() => toast.success('操作成功')}>
|
||
成功提示
|
||
</Button>
|
||
<Button onClick={() => toast.error('操作失败')}>
|
||
错误提示
|
||
</Button>
|
||
<Button onClick={() => toast.info('新消息', {
|
||
title: '系统通知',
|
||
action: {
|
||
label: '查看',
|
||
onClick: () => console.log('查看消息')
|
||
}
|
||
})}>
|
||
信息提示
|
||
</Button>
|
||
</div>
|
||
|
||
<ToastContainer
|
||
toasts={toast.toasts}
|
||
onClose={toast.removeToast}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
## 动画组件
|
||
|
||
### AnimatedEntry 进入动画
|
||
|
||
```typescript
|
||
import AnimatedEntry, { SlideInUp, StaggeredAnimation } from '@/components/ui/AnimatedEntry'
|
||
|
||
function AnimationExample() {
|
||
return (
|
||
<div className="space-y-8">
|
||
{/* 基础进入动画 */}
|
||
<AnimatedEntry direction="up" duration="normal">
|
||
<Card className="p-6">
|
||
<h3>从下方滑入的卡片</h3>
|
||
</Card>
|
||
</AnimatedEntry>
|
||
|
||
{/* 预配置动画 */}
|
||
<SlideInUp delay={200}>
|
||
<div>延迟 200ms 的滑入动画</div>
|
||
</SlideInUp>
|
||
|
||
{/* 交错动画 */}
|
||
<StaggeredAnimation staggerDelay={100}>
|
||
{[1, 2, 3, 4].map(i => (
|
||
<Card key={i} className="p-4">
|
||
<p>卡片 {i}</p>
|
||
</Card>
|
||
))}
|
||
</StaggeredAnimation>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
**最佳实践:**
|
||
- 使用 `trigger="visible"` 实现滚动触发动画
|
||
- 交错动画的延迟时间建议 50-150ms
|
||
- 响应用户的减少动画偏好设置
|
||
|
||
## 可访问性工具
|
||
|
||
### 用户偏好设置
|
||
|
||
```typescript
|
||
import { PreferencesProvider, AccessibilityPanel, useUserPreferences } from '@/lib/preferences'
|
||
|
||
function App() {
|
||
return (
|
||
<PreferencesProvider>
|
||
<div className="app">
|
||
<AccessibilityPanel />
|
||
<MyContent />
|
||
</div>
|
||
</PreferencesProvider>
|
||
)
|
||
}
|
||
|
||
function MyContent() {
|
||
const { preferences } = useUserPreferences()
|
||
|
||
return (
|
||
<div>
|
||
<p>当前字体大小: {preferences.fontSize}%</p>
|
||
<p>减少动画: {preferences.prefersReducedMotion ? '是' : '否'}</p>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 包容性设计
|
||
|
||
```typescript
|
||
import { createInclusiveStatusIndicator } from '@/lib/accessibility'
|
||
|
||
function StatusExample() {
|
||
const successIndicator = createInclusiveStatusIndicator('success', {
|
||
showIcon: true,
|
||
showShape: true,
|
||
size: 'medium'
|
||
})
|
||
|
||
return (
|
||
<div
|
||
className="status-indicator success shape-circle"
|
||
style={successIndicator.style}
|
||
aria-label={successIndicator.ariaLabel}
|
||
>
|
||
{successIndicator.icon}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
## 性能优化
|
||
|
||
### 虚拟滚动
|
||
|
||
```typescript
|
||
import VirtualList from '@/components/ui/VirtualList'
|
||
|
||
function VirtualListExample() {
|
||
const items = Array.from({ length: 10000 }, (_, i) => ({
|
||
id: i,
|
||
name: `项目 ${i + 1}`,
|
||
description: `描述 ${i + 1}`
|
||
}))
|
||
|
||
return (
|
||
<VirtualList
|
||
items={items}
|
||
itemHeight={60}
|
||
height={400}
|
||
renderItem={(item, index) => (
|
||
<div className="flex items-center p-4 border-b">
|
||
<div className="flex-1">
|
||
<h4>{item.name}</h4>
|
||
<p className="text-sm text-gray-500">{item.description}</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 性能监控
|
||
|
||
```typescript
|
||
import { usePerformanceMonitor, useMemoryMonitor } from '@/lib/performance'
|
||
|
||
function PerformanceExample() {
|
||
usePerformanceMonitor('MyComponent')
|
||
const memoryInfo = useMemoryMonitor()
|
||
|
||
return (
|
||
<div>
|
||
{memoryInfo && (
|
||
<p>内存使用: {Math.round(memoryInfo.usedJSHeapSize / 1024 / 1024)}MB</p>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
## 迁移指南
|
||
|
||
### 从传统 CSS 迁移
|
||
|
||
```typescript
|
||
// 旧方式
|
||
<div className="bg-blue-500 text-white p-4 rounded-lg">
|
||
内容
|
||
</div>
|
||
|
||
// 新方式
|
||
<Card variant="filled" className="bg-apple-blue-light text-white">
|
||
<div className="p-apple-4">
|
||
内容
|
||
</div>
|
||
</Card>
|
||
```
|
||
|
||
### 从其他 UI 库迁移
|
||
|
||
```typescript
|
||
// Material-UI Button
|
||
<Button variant="contained" color="primary">
|
||
按钮
|
||
</Button>
|
||
|
||
// Apple UI Button
|
||
<Button variant="filled">
|
||
按钮
|
||
</Button>
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
### Q: 如何自定义主题颜色?
|
||
A: 修改 `tailwind.config.ts` 中的颜色令牌,或在 CSS 中覆盖 CSS 变量。
|
||
|
||
### Q: 动画不生效怎么办?
|
||
A: 检查用户是否启用了减少动画偏好,或者组件是否设置了 `disabled` 属性。
|
||
|
||
### Q: 如何确保可访问性?
|
||
A: 使用语义化 HTML,提供 ARIA 属性,确保键盘导航,测试对比度。
|
||
|
||
### Q: 性能优化建议?
|
||
A: 使用 React.memo,避免不必要的重渲染,大列表使用虚拟滚动。
|
||
|
||
## 总结
|
||
|
||
Apple 风格 UI 设计系统提供了完整的组件库和工具集,遵循苹果 Human Interface Guidelines 2025 标准。通过合理使用这些组件和工具,可以构建出美观、易用、高性能的用户界面。
|
||
|
||
记住始终考虑可访问性、性能和用户体验,让每个用户都能享受到优质的交互体验。 |