667 lines
13 KiB
Markdown
667 lines
13 KiB
Markdown
# Common Component Patterns
|
|
|
|
Quick copy-paste patterns for frequent UI components.
|
|
|
|
## 🔘 Buttons
|
|
|
|
### Primary CTA Button
|
|
|
|
```vue
|
|
<button
|
|
class="
|
|
bg-primary text-white
|
|
px-6 py-3 rounded-8px
|
|
min-h-44px
|
|
font-600 text-16px
|
|
transition-colors duration-200
|
|
hover:bg-[#e65a00]
|
|
active:bg-[#e65a00]
|
|
cursor-pointer
|
|
disabled:opacity-60 disabled:cursor-not-allowed
|
|
"
|
|
:disabled="loading"
|
|
@click="handleSubmit"
|
|
>
|
|
<i v-if="loading" class="i-mdi-loading animate-spin w-20px h-20px mr-2"></i>
|
|
{{ loading ? '提交中...' : '确认提交' }}
|
|
</button>
|
|
```
|
|
|
|
### Secondary/Outline Button
|
|
|
|
```vue
|
|
<button
|
|
class="
|
|
bg-transparent border-2 border-primary text-primary
|
|
px-6 py-3 rounded-8px
|
|
min-h-44px
|
|
font-600 text-16px
|
|
transition-all duration-200
|
|
hover:bg-primary hover:text-white
|
|
cursor-pointer
|
|
"
|
|
@click="handleCancel"
|
|
>
|
|
取消
|
|
</button>
|
|
```
|
|
|
|
### Icon Button (Accessible)
|
|
|
|
```vue
|
|
<button
|
|
aria-label="关闭"
|
|
class="
|
|
min-h-44px min-w-44px
|
|
center
|
|
rounded-full
|
|
transition-colors duration-200
|
|
hover:bg-gray-100
|
|
cursor-pointer
|
|
"
|
|
@click="handleClose"
|
|
>
|
|
<i class="i-mdi-close w-24px h-24px text-gray-600"></i>
|
|
</button>
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 Cards
|
|
|
|
### Glass Card (Primary Style)
|
|
|
|
```vue
|
|
<view
|
|
class="
|
|
bg-white/80 dark:bg-white/10
|
|
backdrop-blur-md
|
|
border border-gray-200 dark:border-white/20
|
|
rounded-16px
|
|
p-6
|
|
shadow-lg
|
|
"
|
|
>
|
|
<text class="text-20px font-600 text-main block mb-2">
|
|
Card Title
|
|
</text>
|
|
<text class="text-14px text-content block">
|
|
Card description content goes here.
|
|
</text>
|
|
</view>
|
|
```
|
|
|
|
### Flat Card (Secondary Style)
|
|
|
|
```vue
|
|
<view
|
|
class="
|
|
bg-white dark:bg-[#2d2d2d]
|
|
border border-gray-200 dark:border-gray-700
|
|
rounded-8px
|
|
p-4
|
|
shadow-sm
|
|
"
|
|
>
|
|
<view class="flex items-center gap-3">
|
|
<i class="i-mdi-information w-24px h-24px text-primary"></i>
|
|
<text class="text-16px text-main">Information</text>
|
|
</view>
|
|
</view>
|
|
```
|
|
|
|
### Interactive Card (Clickable)
|
|
|
|
```vue
|
|
<view
|
|
class="
|
|
bg-white/80 dark:bg-white/10
|
|
backdrop-blur-md
|
|
border border-gray-200 dark:border-white/20
|
|
rounded-16px
|
|
p-6
|
|
shadow-lg
|
|
transition-colors duration-200
|
|
hover:bg-white/90 dark:hover:bg-white/15
|
|
cursor-pointer
|
|
"
|
|
@click="handleCardClick"
|
|
>
|
|
<view class="flex items-center justify-between">
|
|
<text class="text-18px font-600 text-main">Clickable Card</text>
|
|
<i class="i-mdi-chevron-right w-24px h-24px text-gray-400"></i>
|
|
</view>
|
|
</view>
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 Forms
|
|
|
|
### Form Input (Accessible)
|
|
|
|
```vue
|
|
<view class="mb-4">
|
|
<label
|
|
for="email-input"
|
|
class="block text-14px font-500 text-main mb-2"
|
|
>
|
|
邮箱地址 <span class="text-error">*</span>
|
|
</label>
|
|
<input
|
|
id="email-input"
|
|
v-model="email"
|
|
type="email"
|
|
inputmode="email"
|
|
class="
|
|
w-full px-4 py-3 min-h-44px
|
|
bg-white dark:bg-[#2d2d2d]
|
|
border-2 border-gray-200 dark:border-gray-700
|
|
rounded-8px
|
|
text-16px text-main
|
|
focus:border-primary focus:outline-none
|
|
transition-colors duration-200
|
|
placeholder:text-gray-400
|
|
"
|
|
placeholder="输入您的邮箱"
|
|
:aria-invalid="!!errors.email"
|
|
:aria-describedby="errors.email ? 'email-error' : undefined"
|
|
/>
|
|
<text
|
|
v-if="errors.email"
|
|
id="email-error"
|
|
role="alert"
|
|
class="text-12px text-error mt-1 block"
|
|
>
|
|
{{ errors.email }}
|
|
</text>
|
|
</view>
|
|
```
|
|
|
|
### Select/Dropdown
|
|
|
|
```vue
|
|
<view class="mb-4">
|
|
<label
|
|
for="category-select"
|
|
class="block text-14px font-500 text-main mb-2"
|
|
>
|
|
选择分类
|
|
</label>
|
|
<select
|
|
id="category-select"
|
|
v-model="selectedCategory"
|
|
class="
|
|
w-full px-4 py-3 min-h-44px
|
|
bg-white dark:bg-[#2d2d2d]
|
|
border-2 border-gray-200 dark:border-gray-700
|
|
rounded-8px
|
|
text-16px text-main
|
|
cursor-pointer
|
|
focus:border-primary focus:outline-none
|
|
"
|
|
>
|
|
<option value="">请选择...</option>
|
|
<option value="1">分类一</option>
|
|
<option value="2">分类二</option>
|
|
</select>
|
|
</view>
|
|
```
|
|
|
|
### Checkbox (Accessible)
|
|
|
|
```vue
|
|
<view class="flex items-center gap-2 min-h-44px">
|
|
<input
|
|
id="agree-terms"
|
|
v-model="agreedToTerms"
|
|
type="checkbox"
|
|
class="
|
|
w-20px h-20px
|
|
accent-primary
|
|
cursor-pointer
|
|
"
|
|
/>
|
|
<label
|
|
for="agree-terms"
|
|
class="text-14px text-content cursor-pointer"
|
|
>
|
|
我同意 <text class="text-primary">服务条款</text> 和 <text class="text-primary">隐私政策</text>
|
|
</label>
|
|
</view>
|
|
```
|
|
|
|
---
|
|
|
|
## 🧭 Navigation
|
|
|
|
### Floating Top Navbar
|
|
|
|
```vue
|
|
<view
|
|
class="
|
|
fixed top-4 left-4 right-4
|
|
bg-white/90 dark:bg-[#1a1a1a]/90
|
|
backdrop-blur-md
|
|
border border-gray-200 dark:border-white/20
|
|
rounded-full
|
|
px-6 py-3
|
|
shadow-lg
|
|
z-50
|
|
"
|
|
>
|
|
<view class="flex justify-between items-center">
|
|
<view class="flex items-center gap-2">
|
|
<i class="i-mdi-home w-24px h-24px text-primary"></i>
|
|
<text class="font-600 text-18px text-main">GT Agent</text>
|
|
</view>
|
|
|
|
<button
|
|
aria-label="打开菜单"
|
|
class="min-h-44px min-w-44px center cursor-pointer"
|
|
@click="toggleMenu"
|
|
>
|
|
<i class="i-mdi-menu w-24px h-24px text-main"></i>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Account for navbar height -->
|
|
<view class="pt-72px">
|
|
<!-- Main content -->
|
|
</view>
|
|
```
|
|
|
|
### Bottom Navigation (Safe Area)
|
|
|
|
```vue
|
|
<view
|
|
class="
|
|
fixed bottom-0 left-0 right-0
|
|
bg-white/90 dark:bg-[#1a1a1a]/90
|
|
backdrop-blur-md
|
|
border-t border-gray-200 dark:border-white/20
|
|
pb-safe
|
|
z-50
|
|
"
|
|
>
|
|
<view class="flex justify-around items-center py-2">
|
|
<button
|
|
v-for="item in navItems"
|
|
:key="item.id"
|
|
:aria-label="item.label"
|
|
:class="{
|
|
'text-primary': currentRoute === item.route,
|
|
'text-gray-400': currentRoute !== item.route,
|
|
}"
|
|
class="
|
|
flex flex-col items-center gap-1
|
|
min-h-44px min-w-44px
|
|
center
|
|
cursor-pointer
|
|
transition-colors duration-200
|
|
"
|
|
@click="navigateTo(item.route)"
|
|
>
|
|
<i :class="item.icon" class="w-24px h-24px"></i>
|
|
<text class="text-12px">{{ item.label }}</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
|
|
<script setup lang="ts">
|
|
const navItems = [
|
|
{ id: 1, label: '首页', icon: 'i-mdi-home', route: '/home' },
|
|
{ id: 2, label: '我的', icon: 'i-mdi-account', route: '/profile' },
|
|
]
|
|
</script>
|
|
```
|
|
|
|
### Breadcrumbs
|
|
|
|
```vue
|
|
<nav aria-label="面包屑导航" class="mb-6">
|
|
<ol class="flex items-center gap-2 text-14px">
|
|
<li>
|
|
<a href="/" class="text-primary hover:underline">首页</a>
|
|
</li>
|
|
<li class="text-gray-400">
|
|
<i class="i-mdi-chevron-right w-16px h-16px"></i>
|
|
</li>
|
|
<li>
|
|
<a href="/products" class="text-primary hover:underline">产品</a>
|
|
</li>
|
|
<li class="text-gray-400">
|
|
<i class="i-mdi-chevron-right w-16px h-16px"></i>
|
|
</li>
|
|
<li class="text-content" aria-current="page">产品详情</li>
|
|
</ol>
|
|
</nav>
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Data Display
|
|
|
|
### List Item (Interactive)
|
|
|
|
```vue
|
|
<view
|
|
class="
|
|
flex items-center justify-between
|
|
p-4
|
|
bg-white dark:bg-[#2d2d2d]
|
|
border-b border-gray-200 dark:border-gray-700
|
|
min-h-44px
|
|
transition-colors duration-200
|
|
hover:bg-gray-50 dark:hover:bg-gray-800
|
|
cursor-pointer
|
|
"
|
|
@click="handleItemClick(item)"
|
|
>
|
|
<view class="flex items-center gap-3 flex-1">
|
|
<i class="i-mdi-file-document w-24px h-24px text-primary"></i>
|
|
<view class="flex-1">
|
|
<text class="text-16px font-500 text-main block">{{ item.title }}</text>
|
|
<text class="text-12px text-tips block mt-1">{{ item.subtitle }}</text>
|
|
</view>
|
|
</view>
|
|
<i class="i-mdi-chevron-right w-24px h-24px text-gray-400"></i>
|
|
</view>
|
|
```
|
|
|
|
### Badge/Tag
|
|
|
|
```vue
|
|
<!-- Status badge -->
|
|
<span
|
|
:class="{
|
|
'bg-success/10 text-success': status === 'active',
|
|
'bg-warning/10 text-warning': status === 'pending',
|
|
'bg-error/10 text-error': status === 'inactive',
|
|
}"
|
|
class="
|
|
inline-flex items-center gap-1
|
|
px-3 py-1
|
|
rounded-full
|
|
text-12px font-500
|
|
"
|
|
>
|
|
<i
|
|
:class="{
|
|
'i-mdi-check-circle': status === 'active',
|
|
'i-mdi-clock': status === 'pending',
|
|
'i-mdi-alert-circle': status === 'inactive',
|
|
}"
|
|
class="w-14px h-14px"
|
|
></i>
|
|
{{ statusText }}
|
|
</span>
|
|
```
|
|
|
|
### Empty State
|
|
|
|
```vue
|
|
<view class="center flex-col py-16">
|
|
<i class="i-mdi-inbox w-64px h-64px text-gray-300 mb-4"></i>
|
|
<text class="text-18px font-500 text-main mb-2">暂无数据</text>
|
|
<text class="text-14px text-tips mb-6">
|
|
当前没有可显示的内容
|
|
</text>
|
|
<button
|
|
class="
|
|
bg-primary text-white
|
|
px-6 py-3 rounded-8px
|
|
min-h-44px
|
|
cursor-pointer
|
|
"
|
|
@click="handleRefresh"
|
|
>
|
|
刷新
|
|
</button>
|
|
</view>
|
|
```
|
|
|
|
---
|
|
|
|
## ⚡ Loading States
|
|
|
|
### Skeleton Loader
|
|
|
|
```vue
|
|
<view class="animate-pulse space-y-4 p-4">
|
|
<!-- Title skeleton -->
|
|
<view class="h-20px bg-gray-200 dark:bg-gray-700 rounded w-3/4"></view>
|
|
|
|
<!-- Content skeletons -->
|
|
<view class="space-y-2">
|
|
<view class="h-16px bg-gray-200 dark:bg-gray-700 rounded"></view>
|
|
<view class="h-16px bg-gray-200 dark:bg-gray-700 rounded w-5/6"></view>
|
|
<view class="h-16px bg-gray-200 dark:bg-gray-700 rounded w-4/6"></view>
|
|
</view>
|
|
|
|
<!-- Image skeleton -->
|
|
<view class="h-200px bg-gray-200 dark:bg-gray-700 rounded"></view>
|
|
</view>
|
|
```
|
|
|
|
### Loading Spinner
|
|
|
|
```vue
|
|
<!-- Inline spinner -->
|
|
<view class="center py-8">
|
|
<i class="i-mdi-loading animate-spin w-32px h-32px text-primary"></i>
|
|
</view>
|
|
|
|
<!-- With text -->
|
|
<view class="center flex-col gap-2 py-8">
|
|
<i class="i-mdi-loading animate-spin w-32px h-32px text-primary"></i>
|
|
<text class="text-14px text-tips">加载中...</text>
|
|
</view>
|
|
|
|
<!-- Full screen overlay -->
|
|
<view
|
|
v-if="loading"
|
|
class="
|
|
fixed inset-0
|
|
bg-black/50
|
|
center flex-col gap-3
|
|
z-999
|
|
"
|
|
>
|
|
<i class="i-mdi-loading animate-spin w-48px h-48px text-white"></i>
|
|
<text class="text-16px text-white">加载中...</text>
|
|
</view>
|
|
```
|
|
|
|
---
|
|
|
|
## 🔔 Alerts & Toasts
|
|
|
|
### Alert Box
|
|
|
|
```vue
|
|
<view
|
|
role="alert"
|
|
:class="{
|
|
'bg-success/10 border-success/30 text-success': type === 'success',
|
|
'bg-warning/10 border-warning/30 text-warning': type === 'warning',
|
|
'bg-error/10 border-error/30 text-error': type === 'error',
|
|
'bg-primary/10 border-primary/30 text-primary': type === 'info',
|
|
}"
|
|
class="
|
|
flex items-start gap-3
|
|
p-4
|
|
border-l-4
|
|
rounded-8px
|
|
mb-4
|
|
"
|
|
>
|
|
<i
|
|
:class="{
|
|
'i-mdi-check-circle': type === 'success',
|
|
'i-mdi-alert': type === 'warning',
|
|
'i-mdi-alert-circle': type === 'error',
|
|
'i-mdi-information': type === 'info',
|
|
}"
|
|
class="w-24px h-24px flex-shrink-0 mt-0.5"
|
|
></i>
|
|
|
|
<view class="flex-1">
|
|
<text class="text-16px font-600 block mb-1">{{ title }}</text>
|
|
<text class="text-14px opacity-80">{{ message }}</text>
|
|
</view>
|
|
|
|
<button
|
|
aria-label="关闭"
|
|
class="min-h-44px min-w-44px center cursor-pointer"
|
|
@click="handleClose"
|
|
>
|
|
<i class="i-mdi-close w-20px h-20px"></i>
|
|
</button>
|
|
</view>
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 Modal/Dialog
|
|
|
|
### Modal Overlay
|
|
|
|
```vue
|
|
<!-- Modal -->
|
|
<view
|
|
v-if="isOpen"
|
|
class="fixed inset-0 z-999 center p-4"
|
|
@click.self="handleClose"
|
|
>
|
|
<!-- Backdrop -->
|
|
<view
|
|
class="absolute inset-0 bg-black/50"
|
|
@click="handleClose"
|
|
></view>
|
|
|
|
<!-- Modal Content -->
|
|
<view
|
|
class="
|
|
relative
|
|
bg-white dark:bg-[#1a1a1a]
|
|
rounded-16px
|
|
p-6
|
|
max-w-md w-full
|
|
shadow-2xl
|
|
animate-fade-in
|
|
"
|
|
role="dialog"
|
|
aria-labelledby="modal-title"
|
|
aria-modal="true"
|
|
>
|
|
<!-- Close button -->
|
|
<button
|
|
aria-label="关闭"
|
|
class="
|
|
absolute top-4 right-4
|
|
min-h-44px min-w-44px
|
|
center
|
|
cursor-pointer
|
|
"
|
|
@click="handleClose"
|
|
>
|
|
<i class="i-mdi-close w-24px h-24px text-gray-400"></i>
|
|
</button>
|
|
|
|
<!-- Title -->
|
|
<text id="modal-title" class="text-24px font-700 text-main block mb-4">
|
|
Modal Title
|
|
</text>
|
|
|
|
<!-- Content -->
|
|
<text class="text-16px text-content block mb-6">
|
|
Modal content goes here.
|
|
</text>
|
|
|
|
<!-- Actions -->
|
|
<view class="flex gap-3">
|
|
<button
|
|
class="
|
|
flex-1
|
|
bg-transparent border-2 border-gray-200
|
|
text-gray-600
|
|
px-6 py-3 rounded-8px
|
|
min-h-44px
|
|
cursor-pointer
|
|
"
|
|
@click="handleClose"
|
|
>
|
|
取消
|
|
</button>
|
|
<button
|
|
class="
|
|
flex-1
|
|
bg-primary text-white
|
|
px-6 py-3 rounded-8px
|
|
min-h-44px
|
|
cursor-pointer
|
|
"
|
|
@click="handleConfirm"
|
|
>
|
|
确认
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
```
|
|
|
|
---
|
|
|
|
## 📐 Layout Utilities
|
|
|
|
### Container
|
|
|
|
```vue
|
|
<view class="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
|
<!-- Centered content with responsive padding -->
|
|
</view>
|
|
```
|
|
|
|
### Responsive Grid
|
|
|
|
```vue
|
|
<view class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<view v-for="item in items" :key="item.id">
|
|
<!-- Grid item -->
|
|
</view>
|
|
</view>
|
|
```
|
|
|
|
### Flex Layouts
|
|
|
|
```vue
|
|
<!-- Horizontal stack -->
|
|
<view class="flex items-center gap-4">
|
|
<div>Item 1</div>
|
|
<div>Item 2</div>
|
|
</view>
|
|
|
|
<!-- Vertical stack -->
|
|
<view class="flex flex-col gap-4">
|
|
<div>Item 1</div>
|
|
<div>Item 2</div>
|
|
</view>
|
|
|
|
<!-- Space between -->
|
|
<view class="flex items-center justify-between">
|
|
<div>Left</div>
|
|
<div>Right</div>
|
|
</view>
|
|
|
|
<!-- Centered -->
|
|
<view class="center">
|
|
<div>Centered content</div>
|
|
</view>
|
|
```
|
|
|
|
---
|
|
|
|
**Full Documentation**: See `.claude/DESIGN-SYSTEM.md`
|