Files
wenzi/frontend/README_REACT.md

633 lines
13 KiB
Markdown
Raw Normal View History

# 🦟 蚊子项目 - React组件库
这是蚊子项目的React组件库提供完整的分享功能集成。
## 📦 安装
```bash
npm install @mosquito/react
# 或
yarn add @mosquito/react
# 或
pnpm add @mosquito/react
```
## 🚀 快速开始
### 基础配置
```typescript
// src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { MosquitoProvider } from '@mosquito/react'
const root = ReactDOM.createRoot(document.getElementById('root')!)
root.render(
<React.StrictMode>
<MosquitoProvider
baseUrl="https://api.your-domain.com"
apiKey="your-api-key"
timeout={10000}
>
<App />
</MosquitoProvider>
</React.StrictMode>
)
```
## 📖 组件文档
### MosquitoShareButton
分享按钮组件,支持一键复制链接到剪贴板。
#### Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| activityId | `number` | - | 活动ID必需 |
| userId | `number` | - | 用户ID必需 |
| template | `string` | 'default' | 分享模板 |
| text | `string` | '分享给好友' | 按钮文字 |
| variant | `'primary'\|'secondary'\|'success'\|'danger'` | 'primary' | 按钮样式 |
| size | `'sm'\|'md'\|'lg'` | 'md' | 按钮大小 |
| disabled | `boolean` | false | 是否禁用 |
#### Events
| 事件 | 参数 | 说明 |
|------|------|------|
| onCopy | - | 链接已复制到剪贴板 |
| onError | `Error` | 获取分享链接失败 |
#### 示例
```tsx
import { MosquitoShareButton } from '@mosquito/react'
import { useState } from 'react'
function SharePage() {
const activityId = 1
const userId = 100
const [message, setMessage] = useState('')
return (
<div>
<MosquitoShareButton
activityId={activityId}
userId={userId}
template="default"
text="立即分享"
variant="primary"
size="lg"
onCopy={() => setMessage('分享链接已复制到剪贴板')}
onError={(error) => setMessage(`错误: ${error.message}`)}
/>
{message && <p>{message}</p>}
</div>
)
}
```
### MosquitoPosterCard
海报展示组件,支持加载状态和错误处理。
#### Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| activityId | `number` | - | 活动ID必需 |
| userId | `number` | - | 用户ID必需 |
| template | `string` | 'default' | 海报模板 |
| width | `string` | '300px' | 宽度 |
| height | `string` | '400px' | 高度 |
| lazy | `boolean` | false | 是否懒加载 |
#### Events
| 事件 | 参数 | 说明 |
|------|------|------|
| onLoad | - | 海报加载完成 |
| onError | `Error` | 海报加载失败 |
| onClick | - | 海报被点击 |
#### 示例
```tsx
import { MosquitoPosterCard } from '@mosquito/react'
import { useState } from 'react'
function PosterPage() {
const activityId = 1
const userId = 100
const [loading, setLoading] = useState(true)
return (
<div>
<MosquitoPosterCard
activityId={activityId}
userId={userId}
template="premium"
width="350px"
height="500px"
lazy
onLoad={() => setLoading(false)}
onError={(error) => console.error('海报加载失败:', error)}
onClick={() => console.log('海报被点击')}
/>
{loading && <p>加载中...</p>}
</div>
)
}
```
### MosquitoLeaderboard
排行榜组件,支持分页和排序。
#### Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| activityId | `number` | - | 活动ID必需 |
| page | `number` | 0 | 页码 |
| pageSize | `number` | 20 | 每页大小 |
| topN | `number` | undefined | 只显示前N名 |
| currentUserId | `number` | undefined | 当前用户ID |
| sortable | `boolean` | false | 是否支持排序 |
| exportable | `boolean` | false | 是否显示导出按钮 |
#### Events
| 事件 | 参数 | 说明 |
|------|------|------|
| onLoad | `entries[]` | 排行榜数据加载完成 |
| onError | `Error` | 排行榜加载失败 |
| onPageChange | `number` | 页码变化 |
| onExport | - | 导出排行榜 |
#### 示例
```tsx
import { MosquitoLeaderboard } from '@mosquito/react'
import { useState } from 'react'
function LeaderboardPage() {
const activityId = 1
const [page, setPage] = useState(0)
const currentUserId = 100
return (
<div>
<MosquitoLeaderboard
activityId={activityId}
page={page}
pageSize={20}
topN={10}
currentUserId={currentUserId}
sortable
exportable
onLoad={(entries) => console.log('加载完成:', entries)}
onError={(error) => console.error('加载失败:', error)}
onPageChange={(newPage) => setPage(newPage)}
onExport={() => console.log('导出排行榜')}
/>
</div>
)
}
```
### MosquitoShareModal
分享弹窗组件,提供多种分享方式。
#### Props
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| activityId | `number` | - | 活动ID必需 |
| userId | `number` | - | 用户ID必需 |
| open | `boolean` | - | 是否打开 |
| onClose | `() => void` | - | 关闭回调 |
| title | `string` | '分享活动' | 弹窗标题 |
#### 示例
```tsx
import { MosquitoShareModal } from '@mosquito/react'
import { useState } from 'react'
function ShareModalPage() {
const activityId = 1
const userId = 100
const [open, setOpen] = useState(false)
return (
<div>
<button onClick={() => setOpen(true)}>打开分享弹窗</button>
<MosquitoShareModal
activityId={activityId}
userId={userId}
open={open}
onClose={() => setOpen(false)}
/>
</div>
)
}
```
## 🔧 Hooks
### useMosquito
核心Hook提供API调用功能。
```typescript
import { useMosquito } from '@mosquito/react'
function MyComponent() {
const {
// 活动管理
getActivity,
createActivity,
// 分享功能
getShareUrl,
getShareMeta,
// 海报功能
getPosterImage,
getPosterHtml,
// 排行榜
getLeaderboard,
exportLeaderboard,
// 状态
loading,
error,
// 配置
config
} = useMosquito()
const handleGetShareUrl = async () => {
try {
const url = await getShareUrl(1, 100)
console.log('分享链接:', url)
} catch (err) {
console.error('获取分享链接失败:', err)
}
}
return (
<button onClick={handleGetShareUrl}>
获取分享链接
</button>
)
}
```
### useShareUrl
获取分享链接的专用Hook。
```typescript
import { useShareUrl } from '@mosquito/react'
function ShareUrlComponent({ activityId, userId }: { activityId: number; userId: number }) {
const { shareUrl, loading, error, fetchShareUrl } = useShareUrl(activityId, userId)
return (
<div>
{loading && <p>加载中...</p>}
{error && <p>错误: {error.message}</p>}
{shareUrl && (
<div>
<p>分享链接: {shareUrl}</p>
<button onClick={() => fetchShareUrl()}>刷新</button>
</div>
)}
</div>
)
}
```
### usePoster
海报功能的专用Hook。
```typescript
import { usePoster } from '@mosquito/react'
function PosterComponent({ activityId, userId }: { activityId: number; userId: number }) {
const { posterUrl, loading, error, fetchPoster } = usePoster(activityId, userId, 'default')
return (
<div>
{loading && <p>加载中...</p>}
{error && <p>错误: {error.message}</p>}
{posterUrl && (
<div>
<img src={posterUrl} alt="分享海报" />
<button onClick={() => fetchPoster()}>刷新海报</button>
</div>
)}
</div>
)
}
```
### useLeaderboard
排行榜的专用Hook。
```typescript
import { useLeaderboard } from '@mosquito/react'
function LeaderboardComponent({ activityId }: { activityId: number }) {
const { entries, loading, error, pagination, fetchLeaderboard, changePage } = useLeaderboard(activityId)
return (
<div>
{loading && <p>加载中...</p>}
{error && <p>错误: {error.message}</p>}
{entries && (
<div>
<ul>
{entries.map((entry, index) => (
<li key={entry.userId}>
#{index + 1} - {entry.userName}: {entry.score}分
</li>
))}
</ul>
<button onClick={() => changePage(pagination.page - 1)} disabled={pagination.page === 0}>
上一页
</button>
<span>第 {pagination.page + 1} 页</span>
<button onClick={() => changePage(pagination.page + 1)} disabled={pagination.page >= pagination.totalPages - 1}>
下一页
</button>
</div>
)}
</div>
)
}
```
## 🎨 自定义主题
### 使用主题提供者
```tsx
import { MosquitoProvider, MosquitoTheme } from '@mosquito/react'
const customTheme: MosquitoTheme = {
colors: {
primary: '#ff6b00',
secondary: '#6c757d',
success: '#28a745',
danger: '#dc3545',
warning: '#ffc107',
},
components: {
Button: {
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
},
Card: {
borderRadius: '12px',
boxShadow: '0 4px 8px rgba(0,0,0,0.15)',
},
},
}
function App() {
return (
<MosquitoProvider
baseUrl="https://api.your-domain.com"
apiKey="your-api-key"
theme={customTheme}
>
<YourApp />
</MosquitoProvider>
)
}
```
## 📝 TypeScript类型
### 类型定义
```typescript
import type {
Activity,
LeaderboardEntry,
ShareMeta,
PosterConfig,
ApiResponse,
MosquitoConfig,
MosquitoTheme
} from '@mosquito/react'
// Activity类型
interface Activity {
id: number
name: string
startTime: Date
endTime: Date
status: 'draft' | 'active' | 'completed'
}
// LeaderboardEntry类型
interface LeaderboardEntry {
userId: number
userName: string
score: number
rank?: number
inviteCount?: number
}
// ShareMeta类型
interface ShareMeta {
title: string
description: string
image: string
url: string
}
// PosterConfig类型
interface PosterConfig {
template: string
imageUrl: string
htmlUrl: string
}
// ApiResponse类型
interface ApiResponse<T> {
code: number
message: string
data: T
meta?: {
page: number
size: number
total: number
totalPages: number
}
}
// MosquitoConfig类型
interface MosquitoConfig {
baseUrl: string
apiKey: string
timeout?: number
retryCount?: number
enableLogging?: boolean
theme?: MosquitoTheme
}
// MosquitoTheme类型
interface MosquitoTheme {
colors: {
primary: string
secondary: string
success: string
danger: string
warning: string
}
components: {
Button?: React.CSSProperties
Card?: React.CSSProperties
}
}
```
## 🧪 测试
### 单元测试
```typescript
import { render, screen, fireEvent } from '@testing-library/react'
import { MosquitoProvider } from '@mosquito/react'
import { MosquitoShareButton } from '@mosquito/react'
function renderWithProviders(ui: React.ReactElement) {
return render(
<MosquitoProvider
baseUrl="https://test-api.com"
apiKey="test-key"
>
{ui}
</MosquitoProvider>
)
}
test('渲染分享按钮', () => {
renderWithProviders(
<MosquitoShareButton activityId={1} userId={100} />
)
const button = screen.getByText('分享给好友')
expect(button).toBeInTheDocument()
})
test('点击按钮触发分享', async () => {
renderWithProviders(
<MosquitoShareButton activityId={1} userId={100} onCopy={mockOnCopy} />
)
const button = screen.getByText('分享给好友')
fireEvent.click(button)
await waitFor(() => {
expect(mockOnCopy).toHaveBeenCalled()
})
})
```
## 📚 最佳实践
### 1. 错误处理
```tsx
function GoodErrorHandling() {
const { getShareUrl, error } = useMosquito()
const [localError, setLocalError] = useState<Error | null>(null)
const handleShare = async () => {
try {
await getShareUrl(1, 100)
} catch (err) {
setLocalError(err as Error)
}
}
return (
<div>
<button onClick={handleShare}>分享</button>
{(error || localError) && (
<div className="error-message">
{(error || localError)?.message}
</div>
)}
</div>
)
}
```
### 2. 加载状态
```tsx
function GoodLoadingState() {
const { getShareUrl, loading } = useMosquito()
return (
<button onClick={() => getShareUrl(1, 100)} disabled={loading}>
{loading ? '分享中...' : '分享给好友'}
</button>
)
}
```
### 3. 类型安全
```tsx
function TypeSafeComponent() {
const [activity, setActivity] = useState<Activity | null>(null)
const { getActivity } = useMosquito()
useEffect(() => {
const loadActivity = async () => {
const data = await getActivity(1)
setActivity(data)
}
loadActivity()
}, [getActivity])
return (
<div>
{activity && (
<div>
<h2>{activity.name}</h2>
<p>开始时间: {activity.startTime.toLocaleDateString()}</p>
<p>结束时间: {activity.endTime.toLocaleDateString()}</p>
</div>
)}
</div>
)
}
```
## 🤝 贡献
欢迎提交Issue和Pull Request
---
*React组件库版本: v2.0.0*
*最后更新: 2026-01-22*