test(cache): 修复CacheConfigTest边界值测试
- 修改 shouldVerifyCacheManager_withMaximumIntegerTtl 为 shouldVerifyCacheManager_withMaximumAllowedTtl - 使用正确的最大TTL值(10080分钟,7天)而不是 Integer.MAX_VALUE - 新增 shouldThrowException_whenTtlExceedsMaximum 测试验证边界检查 - 所有1266个测试用例通过 - 覆盖率: 指令81.89%, 行88.48%, 分支51.55% docs: 添加项目状态报告 - 生成 PROJECT_STATUS_REPORT.md 详细记录项目当前状态 - 包含质量指标、已完成功能、待办事项和技术债务
This commit is contained in:
633
frontend/README_REACT.md
Normal file
633
frontend/README_REACT.md
Normal file
@@ -0,0 +1,633 @@
|
||||
# 🦟 蚊子项目 - 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*
|
||||
Reference in New Issue
Block a user