1100 lines
20 KiB
Markdown
1100 lines
20 KiB
Markdown
# GameGroup 组件设计文档
|
|
|
|
**项目名称**: GameGroup 前端系统
|
|
**文档版本**: v1.0
|
|
**更新时间**: 2026-01-28
|
|
|
|
---
|
|
|
|
## 📋 目录
|
|
|
|
- [1. 组件概述](#1-组件概述)
|
|
- [2. 基础组件](#2-基础组件)
|
|
- [3. 业务组件](#3-业务组件)
|
|
- [4. 布局组件](#4-布局组件)
|
|
- [5. 组件通信](#5-组件通信)
|
|
- [6. 组件性能优化](#6-组件性能优化)
|
|
|
|
---
|
|
|
|
## 1. 组件概述
|
|
|
|
### 1.1 组件分类
|
|
|
|
```
|
|
组件层级
|
|
├── 布局组件 (Layout)
|
|
│ ├── MainLayout # 主布局
|
|
│ ├── EmptyLayout # 空白布局
|
|
│ └── AuthLayout # 认证布局
|
|
│
|
|
├── 容器组件 (Container)
|
|
│ ├── AppHeader # 顶部导航
|
|
│ ├── AppSidebar # 侧边栏
|
|
│ └── AppFooter # 页脚
|
|
│
|
|
├── 业务组件 (Business)
|
|
│ ├── UserCard # 用户卡片
|
|
│ ├── GroupCard # 小组卡片
|
|
│ ├── GameCard # 游戏卡片
|
|
│ ├── AppointmentCard # 预约卡片
|
|
│ └── HonorBadge # 荣誉徽章
|
|
│
|
|
└── 基础组件 (Base)
|
|
├── Button # 按钮
|
|
├── Input # 输入框
|
|
├── Select # 下拉选择
|
|
├── Modal # 弹窗
|
|
├── Drawer # 抽屉
|
|
├── Table # 表格
|
|
├── Form # 表单
|
|
├── Upload # 上传
|
|
└── ...
|
|
```
|
|
|
|
### 1.2 命名规范
|
|
|
|
- **单文件组件**: PascalCase (UserCard.vue)
|
|
- **组件注册**: PascalCase
|
|
- **props**: camelCase
|
|
- **events**: kebab-case
|
|
|
|
### 1.3 组件结构
|
|
|
|
```vue
|
|
<template>
|
|
<!-- 模板内容 -->
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
// 1. 导入
|
|
import { ref, computed } from 'vue'
|
|
|
|
// 2. Props定义
|
|
interface Props {
|
|
// ...
|
|
}
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
// ...
|
|
})
|
|
|
|
// 3. Emits定义
|
|
interface Emits {
|
|
// ...
|
|
}
|
|
const emit = defineEmits<Emits>()
|
|
|
|
// 4. 响应式数据
|
|
const state = ref()
|
|
|
|
// 5. 计算属性
|
|
const computed = computed(() => {})
|
|
|
|
// 6. 方法
|
|
const method = () => {}
|
|
|
|
// 7. 生命周期
|
|
onMounted(() => {})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
// 样式
|
|
</style>
|
|
```
|
|
|
|
---
|
|
|
|
## 2. 基础组件
|
|
|
|
### 2.1 Button 按钮
|
|
|
|
#### 功能特性
|
|
- 支持多种类型 (primary, secondary, text, danger)
|
|
- 支持多种尺寸 (large, medium, small)
|
|
- 支持图标
|
|
- 支持加载状态
|
|
- 支持禁用状态
|
|
- 支持按钮组
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface ButtonProps {
|
|
type?: 'primary' | 'secondary' | 'text' | 'danger'
|
|
size?: 'large' | 'medium' | 'small'
|
|
icon?: string
|
|
loading?: boolean
|
|
disabled?: boolean
|
|
long?: boolean /* 长按钮,宽度100% */
|
|
nativeType?: 'button' | 'submit' | 'reset'
|
|
}
|
|
|
|
interface ButtonEmits {
|
|
click: [event: MouseEvent]
|
|
}
|
|
```
|
|
|
|
#### 使用示例
|
|
|
|
```vue
|
|
<template>
|
|
<!-- 基础用法 -->
|
|
<Button type="primary">主要按钮</Button>
|
|
<Button type="secondary">次要按钮</Button>
|
|
<Button type="text">文字按钮</Button>
|
|
|
|
<!-- 尺寸 -->
|
|
<Button size="large">大按钮</Button>
|
|
<Button size="medium">默认按钮</Button>
|
|
<Button size="small">小按钮</Button>
|
|
|
|
<!-- 图标 -->
|
|
<Button icon="plus">添加</Button>
|
|
|
|
<!-- 加载状态 -->
|
|
<Button loading>加载中...</Button>
|
|
|
|
<!-- 按钮组 -->
|
|
<ButtonGroup>
|
|
<Button>左</Button>
|
|
<Button>中</Button>
|
|
<Button>右</Button>
|
|
</ButtonGroup>
|
|
</template>
|
|
```
|
|
|
|
#### 实现代码
|
|
|
|
```vue
|
|
<!-- components/base/Button/Button.vue -->
|
|
<template>
|
|
<button
|
|
:class="buttonClass"
|
|
:disabled="disabled || loading"
|
|
:type="nativeType"
|
|
@click="handleClick"
|
|
>
|
|
<Icon v-if="loading" name="loading" class="is-loading" />
|
|
<Icon v-else-if="icon" :name="icon" />
|
|
<span v-if="$slots.default"><slot /></span>
|
|
</button>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import type { ButtonProps, ButtonEmits } from './types'
|
|
|
|
const props = withDefaults(defineProps<ButtonProps>(), {
|
|
type: 'default',
|
|
size: 'medium',
|
|
nativeType: 'button'
|
|
})
|
|
|
|
const emit = defineEmits<ButtonEmits>()
|
|
|
|
const buttonClass = computed(() => [
|
|
'g-button',
|
|
`g-button--${props.type}`,
|
|
`g-button--${props.size}`,
|
|
{
|
|
'is-disabled': props.disabled,
|
|
'is-loading': props.loading,
|
|
'is-long': props.long
|
|
}
|
|
])
|
|
|
|
const handleClick = (e: MouseEvent) => {
|
|
if (!props.disabled && !props.loading) {
|
|
emit('click', e)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.g-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
|
|
&:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
// 类型
|
|
&--primary {
|
|
background: var(--color-primary-500);
|
|
color: white;
|
|
|
|
&:hover:not(.is-disabled) {
|
|
background: var(--color-primary-600);
|
|
}
|
|
}
|
|
|
|
&--secondary {
|
|
background: transparent;
|
|
color: var(--color-primary-500);
|
|
border: 2px solid var(--color-primary-500);
|
|
|
|
&:hover:not(.is-disabled) {
|
|
background: var(--color-primary-50);
|
|
}
|
|
}
|
|
|
|
// 尺寸
|
|
&--large {
|
|
height: 48px;
|
|
padding: 0 24px;
|
|
font-size: 16px;
|
|
}
|
|
|
|
&--medium {
|
|
height: 40px;
|
|
padding: 0 20px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
&--small {
|
|
height: 32px;
|
|
padding: 0 16px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
// 状态
|
|
&.is-disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
&.is-loading {
|
|
pointer-events: none;
|
|
}
|
|
|
|
&.is-long {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
```
|
|
|
|
---
|
|
|
|
### 2.2 Input 输入框
|
|
|
|
#### 功能特性
|
|
- 支持前缀/后缀图标
|
|
- 支持清除按钮
|
|
- 支持字数统计
|
|
- 支持搜索模式
|
|
- 支持多行文本域
|
|
- 支持密码显示切换
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface InputProps {
|
|
modelValue: string | number
|
|
type?: 'text' | 'password' | 'textarea' | 'search'
|
|
placeholder?: string
|
|
disabled?: boolean
|
|
clearable?: boolean
|
|
showPassword?: boolean
|
|
prefixIcon?: string
|
|
suffixIcon?: string
|
|
maxlength?: number
|
|
rows?: number /* textarea行数 */
|
|
size?: 'large' | 'medium' | 'small'
|
|
}
|
|
|
|
interface InputEmits {
|
|
'update:modelValue': [value: string]
|
|
'change': [value: string]
|
|
'focus': [event: FocusEvent]
|
|
'blur': [event: FocusEvent]
|
|
'clear': []
|
|
}
|
|
```
|
|
|
|
#### 使用示例
|
|
|
|
```vue
|
|
<template>
|
|
<!-- 基础用法 -->
|
|
<Input v-model="value" placeholder="请输入内容" />
|
|
|
|
<!-- 可清除 -->
|
|
<Input v-model="value" clearable />
|
|
|
|
<!-- 密码框 -->
|
|
<Input v-model="password" type="password" show-password />
|
|
|
|
<!-- 带图标 -->
|
|
<Input v-model="email" prefix-icon="email" />
|
|
|
|
<!-- 文本域 -->
|
|
<Input v-model="content" type="textarea" :rows="4" />
|
|
|
|
<!-- 字数限制 -->
|
|
<Input v-model="text" :maxlength="100" show-word-limit />
|
|
</template>
|
|
```
|
|
|
|
---
|
|
|
|
### 2.3 Modal 弹窗
|
|
|
|
#### 功能特性
|
|
- 支持自定义头部/底部
|
|
- 支持多种尺寸
|
|
- 支持全屏
|
|
- 支持遮罩层点击关闭
|
|
- 支持键盘ESC关闭
|
|
- 支持嵌套弹窗
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface ModalProps {
|
|
visible?: boolean
|
|
title?: string
|
|
width?: string | number
|
|
fullscreen?: boolean
|
|
top?: string /* 距离顶部距离 */
|
|
modal?: boolean /* 是否显示遮罩 */
|
|
lockScroll?: boolean /* 是否锁定滚动 */
|
|
closeOnClickModal?: boolean
|
|
closeOnPressEscape?: boolean
|
|
showClose?: boolean
|
|
beforeClose?: (done: () => void) => void
|
|
}
|
|
|
|
interface ModalEmits {
|
|
'update:visible': [value: boolean]
|
|
open: []
|
|
opened: []
|
|
close: []
|
|
closed: []
|
|
}
|
|
|
|
interface ModalSlots {
|
|
header?: () => VNode
|
|
default?: () => VNode
|
|
footer?: () => VNode
|
|
}
|
|
```
|
|
|
|
#### 使用示例
|
|
|
|
```vue
|
|
<template>
|
|
<Modal
|
|
v-model:visible="visible"
|
|
title="提示"
|
|
width="500px"
|
|
:before-close="handleBeforeClose"
|
|
>
|
|
<p>这是一段内容</p>
|
|
<template #footer>
|
|
<Button @click="visible = false">取消</Button>
|
|
<Button type="primary" @click="handleConfirm">确定</Button>
|
|
</template>
|
|
</Modal>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
|
|
const visible = ref(false)
|
|
|
|
const handleBeforeClose = (done: () => void) => {
|
|
// 关闭前确认
|
|
done()
|
|
}
|
|
|
|
const handleConfirm = () => {
|
|
visible.value = false
|
|
}
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
### 2.4 Card 卡片
|
|
|
|
#### 功能特性
|
|
- 支持阴影配置
|
|
- 支持边框配置
|
|
- 支持图片封面
|
|
- 支持悬停效果
|
|
- 支持可点击
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface CardProps {
|
|
shadow?: 'always' | 'hover' | 'never'
|
|
bodyStyle?: object /* body样式 */
|
|
header?: string /* 标题 */
|
|
cover?: string /* 封面图 */
|
|
clickable?: boolean /* 可点击 */
|
|
hoverable?: boolean /* 悬停效果 */
|
|
}
|
|
|
|
interface CardSlots {
|
|
header?: () => VNode
|
|
default?: () => VNode
|
|
cover?: () => VNode
|
|
extra?: () => VNode /* 头部额外内容 */
|
|
}
|
|
```
|
|
|
|
#### 使用示例
|
|
|
|
```vue
|
|
<template>
|
|
<!-- 基础卡片 -->
|
|
<Card>
|
|
<p>卡片内容</p>
|
|
</Card>
|
|
|
|
<!-- 带标题 -->
|
|
<Card header="卡片标题">
|
|
<p>卡片内容</p>
|
|
<template #extra>
|
|
<Button size="small">更多</Button>
|
|
</template>
|
|
</Card>
|
|
|
|
<!-- 图片卡片 -->
|
|
<Card :cover="imageUrl" hoverable>
|
|
<template #cover>
|
|
<img :src="imageUrl" alt="cover" />
|
|
</template>
|
|
<h4>卡片标题</h4>
|
|
<p>卡片描述</p>
|
|
</Card>
|
|
</template>
|
|
```
|
|
|
|
---
|
|
|
|
### 2.5 Table 表格
|
|
|
|
#### 功能特性
|
|
- 支持自定义列
|
|
- 支持排序
|
|
- 支持选择
|
|
- 支持分页
|
|
- 支持加载状态
|
|
- 支持展开行
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface TableProps<T = any> {
|
|
data: T[]
|
|
columns: Column[]
|
|
stripe?: boolean /* 斑马纹 */
|
|
border?: boolean
|
|
showHeader?: boolean
|
|
highlightCurrentRow?: boolean
|
|
rowKey?: string | ((row: T) => string)
|
|
defaultSort?: { prop: string; order: 'ascending' | 'descending' }
|
|
}
|
|
|
|
interface Column {
|
|
prop: string
|
|
label: string
|
|
width?: number | string
|
|
minWidth?: number | string
|
|
align?: 'left' | 'center' | 'right'
|
|
sortable?: boolean
|
|
fixed?: 'left' | 'right'
|
|
formatter?: (row: any, column: Column, value: any) => string
|
|
slots?: { default: (scope: { row: any; $index: number }) => VNode }
|
|
}
|
|
```
|
|
|
|
#### 使用示例
|
|
|
|
```vue
|
|
<template>
|
|
<Table :data="tableData" :columns="columns" stripe border>
|
|
<template #action="{ row }">
|
|
<Button size="small" @click="handleEdit(row)">编辑</Button>
|
|
<Button size="small" type="danger" @click="handleDelete(row)">删除</Button>
|
|
</template>
|
|
</Table>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
|
|
interface User {
|
|
id: string
|
|
name: string
|
|
email: string
|
|
}
|
|
|
|
const tableData = ref<User[]>([])
|
|
|
|
const columns = [
|
|
{ prop: 'name', label: '姓名', width: 120 },
|
|
{ prop: 'email', label: '邮箱' },
|
|
{ prop: 'action', label: '操作', width: 200, slots: { default: 'action' } }
|
|
]
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## 3. 业务组件
|
|
|
|
### 3.1 UserCard 用户卡片
|
|
|
|
#### 功能特性
|
|
- 显示用户头像、昵称、角色
|
|
- 显示会员状态
|
|
- 显示在线状态
|
|
- 支持快捷操作
|
|
- 支持点击跳转
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface UserCardProps {
|
|
user: {
|
|
id: string
|
|
username: string
|
|
nickname?: string
|
|
avatar: string
|
|
role?: string
|
|
isMember?: boolean
|
|
isOnline?: boolean
|
|
}
|
|
showActions?: boolean /* 显示操作按钮 */
|
|
clickable?: boolean /* 可点击 */
|
|
}
|
|
|
|
interface UserCardEmits {
|
|
click: [user: User]
|
|
chat: [userId: string]
|
|
}
|
|
```
|
|
|
|
#### 使用示例
|
|
|
|
```vue
|
|
<template>
|
|
<UserCard
|
|
:user="userInfo"
|
|
:show-actions="true"
|
|
@click="handleUserClick"
|
|
@chat="handleChat"
|
|
/>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
|
|
const userInfo = ref({
|
|
id: '1',
|
|
username: 'johndoe',
|
|
nickname: 'John',
|
|
avatar: 'https://...',
|
|
isMember: true,
|
|
isOnline: true
|
|
})
|
|
|
|
const handleUserClick = (user: User) => {
|
|
// 跳转到用户详情
|
|
}
|
|
|
|
const handleChat = (userId: string) => {
|
|
// 打开聊天
|
|
}
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
### 3.2 GroupCard 小组卡片
|
|
|
|
#### 功能特性
|
|
- 显示小组头像、名称
|
|
- 显示成员数量
|
|
- 显示用户角色
|
|
- 支持快速加入
|
|
- 显示小组标签
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface GroupCardProps {
|
|
group: {
|
|
id: string
|
|
name: string
|
|
avatar: string
|
|
description?: string
|
|
currentMembers: number
|
|
maxMembers: number
|
|
myRole?: 'owner' | 'admin' | 'member'
|
|
tags?: string[]
|
|
}
|
|
showJoinButton?: boolean
|
|
showMemberCount?: boolean
|
|
}
|
|
|
|
interface GroupCardEmits {
|
|
click: [group: Group]
|
|
join: [groupId: string]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3.3 GameCard 游戏卡片
|
|
|
|
#### 功能特性
|
|
- 显示游戏封面
|
|
- 显示游戏名称
|
|
- 显示游戏类型标签
|
|
- 显示人数范围
|
|
- 显示平台信息
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface GameCardProps {
|
|
game: {
|
|
id: string
|
|
name: string
|
|
coverUrl: string
|
|
description?: string
|
|
tags: string[]
|
|
minPlayers: number
|
|
maxPlayers: number
|
|
platform: string
|
|
}
|
|
showCover?: boolean
|
|
showTags?: boolean
|
|
clickable?: boolean
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3.4 AppointmentCard 预约卡片
|
|
|
|
#### 功能特性
|
|
- 显示预约时间
|
|
- 显示参与人数
|
|
- 显示游戏信息
|
|
- 显示预约状态
|
|
- 支持快速加入/退出
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface AppointmentCardProps {
|
|
appointment: {
|
|
id: string
|
|
title: string
|
|
startTime: string
|
|
endTime: string
|
|
currentParticipants: number
|
|
maxParticipants: number
|
|
status: 'open' | 'full' | 'cancelled' | 'finished'
|
|
game: Game
|
|
isParticipant: boolean
|
|
isCreator: boolean
|
|
}
|
|
showActions?: boolean
|
|
}
|
|
|
|
interface AppointmentCardEmits {
|
|
join: [appointmentId: string]
|
|
leave: [appointmentId: string]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3.5 HonorBadge 荣誉徽章
|
|
|
|
#### 功能特性
|
|
- 显示荣誉图标
|
|
- 显示荣誉名称
|
|
- 显示获得者
|
|
- 动画效果
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface HonorBadgeProps {
|
|
honor: {
|
|
id: string
|
|
title: string
|
|
type: 'YEARLY' | 'MONTHLY' | 'WEEKLY'
|
|
year?: number
|
|
icon?: string
|
|
}
|
|
size?: 'small' | 'medium' | 'large'
|
|
animated?: boolean
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 布局组件
|
|
|
|
### 4.1 MainLayout 主布局
|
|
|
|
#### 功能特性
|
|
- 响应式布局
|
|
- 侧边栏折叠
|
|
- 顶部导航栏
|
|
- 内容区域
|
|
|
|
#### 结构
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ AppHeader │
|
|
├──────────┬──────────────────────────────┤
|
|
│ │ │
|
|
│ │ │
|
|
│ App │ RouterView │
|
|
│ Sidebar │ │
|
|
│ │ │
|
|
│ │ │
|
|
└──────────┴──────────────────────────────┘
|
|
```
|
|
|
|
#### 使用示例
|
|
|
|
```vue
|
|
<template>
|
|
<MainLayout>
|
|
<RouterView />
|
|
</MainLayout>
|
|
</template>
|
|
```
|
|
|
|
---
|
|
|
|
### 4.2 AppHeader 顶部导航
|
|
|
|
#### 功能特性
|
|
- Logo展示
|
|
- 导航菜单
|
|
- 搜索框
|
|
- 用户信息
|
|
- 消息通知
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface AppHeaderProps {
|
|
showLogo?: boolean
|
|
showSearch?: boolean
|
|
showNotification?: boolean
|
|
fixed?: boolean
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4.3 AppSidebar 侧边栏
|
|
|
|
#### 功能特性
|
|
- 可折叠
|
|
- 菜单导航
|
|
- 用户信息卡片
|
|
- 小组快捷入口
|
|
|
|
#### API
|
|
|
|
```typescript
|
|
interface AppSidebarProps {
|
|
collapsed?: boolean
|
|
menuItems: MenuItem[]
|
|
}
|
|
|
|
interface MenuItem {
|
|
path: string
|
|
icon?: string
|
|
label: string
|
|
children?: MenuItem[]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 组件通信
|
|
|
|
### 5.1 Props / Emits
|
|
|
|
父子组件通信:
|
|
|
|
```vue
|
|
<!-- 父组件 -->
|
|
<template>
|
|
<ChildComponent
|
|
:message="parentMessage"
|
|
@update="handleUpdate"
|
|
/>
|
|
</template>
|
|
|
|
<!-- 子组件 -->
|
|
<script setup lang="ts">
|
|
interface Props {
|
|
message: string
|
|
}
|
|
|
|
interface Emits {
|
|
update: [value: string]
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
const emit = defineEmits<Emits>()
|
|
|
|
// 发送事件
|
|
emit('update', 'new value')
|
|
</script>
|
|
```
|
|
|
|
### 5.2 Provide / Inject
|
|
|
|
跨层级组件通信:
|
|
|
|
```vue
|
|
<!-- 祖先组件 -->
|
|
<script setup lang="ts">
|
|
import { provide } from 'vue'
|
|
|
|
provide('theme', {
|
|
color: 'purple',
|
|
size: 'large'
|
|
})
|
|
</script>
|
|
|
|
<!-- 后代组件 -->
|
|
<script setup lang="ts">
|
|
import { inject } from 'vue'
|
|
|
|
const theme = inject('theme', {
|
|
color: 'blue',
|
|
size: 'medium'
|
|
})
|
|
</script>
|
|
```
|
|
|
|
### 5.3 Slots
|
|
|
|
插槽传递:
|
|
|
|
```vue
|
|
<!-- 父组件 -->
|
|
<template>
|
|
<ChildComponent>
|
|
<template #header>
|
|
<h1>自定义标题</h1>
|
|
</template>
|
|
|
|
<p>默认内容</p>
|
|
|
|
<template #footer="{ close }">
|
|
<Button @click="close">关闭</Button>
|
|
</template>
|
|
</ChildComponent>
|
|
</template>
|
|
|
|
<!-- 子组件 -->
|
|
<template>
|
|
<div>
|
|
<slot name="header" />
|
|
<slot />
|
|
<slot name="footer" :close="handleClose" />
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### 5.4 Event Bus
|
|
|
|
兄弟组件通信:
|
|
|
|
```typescript
|
|
// utils/eventBus.ts
|
|
import { ref } from 'vue'
|
|
|
|
type EventBus = Record<string, any[]>
|
|
|
|
const bus = ref<EventBus>({})
|
|
|
|
export function useEventBus() {
|
|
const emit = (event: string, ...args: any[]) => {
|
|
if (bus.value[event]) {
|
|
bus.value[event].forEach((fn) => fn(...args))
|
|
}
|
|
}
|
|
|
|
const on = (event: string, callback: Function) => {
|
|
if (!bus.value[event]) {
|
|
bus.value[event] = []
|
|
}
|
|
bus.value[event].push(callback)
|
|
}
|
|
|
|
const off = (event: string, callback?: Function) => {
|
|
if (!callback) {
|
|
delete bus.value[event]
|
|
} else {
|
|
bus.value[event] = bus.value[event].filter((fn) => fn !== callback)
|
|
}
|
|
}
|
|
|
|
return { emit, on, off }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 组件性能优化
|
|
|
|
### 6.1 懒加载
|
|
|
|
动态导入组件:
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { defineAsyncComponent } from 'vue'
|
|
|
|
const HeavyComponent = defineAsyncComponent(() =>
|
|
import('./HeavyComponent.vue')
|
|
)
|
|
</script>
|
|
```
|
|
|
|
### 6.2 KeepAlive
|
|
|
|
缓存组件状态:
|
|
|
|
```vue
|
|
<template>
|
|
<KeepAlive :include="['GroupList', 'GameList']">
|
|
<RouterView />
|
|
</KeepAlive>
|
|
</template>
|
|
```
|
|
|
|
### 6.3 v-once
|
|
|
|
静态内容只渲染一次:
|
|
|
|
```vue
|
|
<template>
|
|
<div v-once>
|
|
<h1>{{ staticTitle }}</h1>
|
|
<p>{{ staticContent }}</p>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### 6.4 computed缓存
|
|
|
|
使用计算属性缓存结果:
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
|
|
const list = ref([...])
|
|
|
|
// 使用computed
|
|
const filteredList = computed(() => {
|
|
return list.value.filter(item => item.active)
|
|
})
|
|
|
|
// 而不是methods
|
|
const getFilteredList = () => {
|
|
return list.value.filter(item => item.active)
|
|
}
|
|
</script>
|
|
```
|
|
|
|
### 6.5 虚拟滚动
|
|
|
|
长列表使用虚拟滚动:
|
|
|
|
```vue
|
|
<template>
|
|
<VirtualList
|
|
:items="largeList"
|
|
:item-height="50"
|
|
:visible-height="600"
|
|
>
|
|
<template #default="{ item }">
|
|
<div>{{ item.name }}</div>
|
|
</template>
|
|
</VirtualList>
|
|
</template>
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 组件测试
|
|
|
|
### 7.1 单元测试
|
|
|
|
```typescript
|
|
// components/base/Button/__tests__/Button.test.ts
|
|
import { mount } from '@vue/test-utils'
|
|
import { describe, it, expect } from 'vitest'
|
|
import Button from '../Button.vue'
|
|
|
|
describe('Button', () => {
|
|
it('renders properly', () => {
|
|
const wrapper = mount(Button, {
|
|
slots: { default: 'Click me' }
|
|
})
|
|
expect(wrapper.text()).toBe('Click me')
|
|
})
|
|
|
|
it('emits click event', async () => {
|
|
const wrapper = mount(Button)
|
|
await wrapper.trigger('click')
|
|
expect(wrapper.emitted('click')).toBeTruthy()
|
|
})
|
|
|
|
it('does not emit click when disabled', async () => {
|
|
const wrapper = mount(Button, {
|
|
props: { disabled: true }
|
|
})
|
|
await wrapper.trigger('click')
|
|
expect(wrapper.emitted('click')).toBeFalsy()
|
|
})
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 总结
|
|
|
|
本组件设计文档确保:
|
|
- ✅ **可复用性**: 组件可在多个场景使用
|
|
- ✅ **可维护性**: 清晰的代码结构和文档
|
|
- ✅ **一致性**: 统一的API和设计风格
|
|
- ✅ **类型安全**: 完整的TypeScript支持
|
|
- ✅ **性能优化**: 合理的优化策略
|
|
|
|
---
|
|
|
|
**文档维护**: 随组件库演进持续更新
|
|
**最后更新**: 2026-01-28
|