@lofter-mission/react
Version:
React 组件库,基于 @lofter-mission/core 实现活动任务相关组件
684 lines (578 loc) • 16.6 kB
Markdown
# @lofter-mission/react
基于 `@lofter-mission/core` 的 React 组件库,提供活动任务相关的通用 UI 组件。
## 安装
```bash
npm install @lofter-mission/react @lofter-mission/core
# 或
yarn add @lofter-mission/react @lofter-mission/core
# 或
pnpm add @lofter-mission/react @lofter-mission/core
```
## 组件概览
### ActivityContainer
活动容器组件,作为所有活动相关组件的根容器,提供活动上下文。必须包裹在所有其他组件的外层。
```tsx
import { ActivityContainer } from '@lofter-mission/react';
import { MissionApiService } from '@lofter-mission/core';
// 如果你已经有了 MissionApiService 实例,可以传入避免重复初始化
const missionApi = new MissionApiService({ activityCode: 'confession' });
<ActivityContainer
activityCode="confession"
presetMissionApiService={missionApi} // 可选,传入预设的API服务实例
>
{/* 其他组件 */}
</ActivityContainer>
```
### ActivityUser
登录用户积分信息组件,显示用户头像、昵称、积分等信息。
```tsx
import { ActivityUser } from '@lofter-mission/react';
<ActivityUser
pointsText="我的积分"
loadingText="加载中..."
errorText="加载失败"
noDataText="暂无数据"
/>
```
### MissionItemView
单个任务组件,展示任务详情和进度。支持多种任务类型包括分享任务、邀请任务等。
```tsx
import { MissionItemView } from '@lofter-mission/react';
<MissionItemView
mission={missionObject}
onProgressUpdate={(mission, points) => {
console.log('任务更新:', mission, '当前积分:', points);
}}
onCustomShare={(mission) => {
// 处理自定义分享任务
console.log('分享任务:', mission);
}}
completedText="已完成"
defaultActionText="开始任务"
/>
```
### MissionListView
任务列表组件,显示多个任务项。可以根据标签筛选任务。
```tsx
import { MissionListView } from '@lofter-mission/react';
<MissionListView
tabName="daily" // 可选,筛选特定标签的任务
onMissionProgressUpdate={(mission, points) => {
console.log('任务进度更新:', mission, '获得积分:', points);
}}
onCustomShare={(mission) => {
// 处理分享类任务的自定义逻辑
console.log('自定义分享:', mission);
}}
loadingText="加载中..."
errorText="加载失败"
emptyText="暂无任务"
/>
```
### MissionPopupView
弹窗组件,包含用户信息和任务列表。支持自定义样式类名。
```tsx
import { MissionPopupView } from '@lofter-mission/react';
<MissionPopupView
visible={showPopup}
onClose={() => setShowPopup(false)}
onMissionProgressUpdate={(mission) => {
console.log('任务完成:', mission);
}}
title="任务中心"
overlayClassName="custom-overlay"
popupClassName="custom-popup"
/>
```
### AwardListView
奖励列表组件,显示多个奖励项。支持积分兑换功能。
```tsx
import { AwardListView } from '@lofter-mission/react';
<AwardListView
onPrizeRedeem={(prize, points) => {
console.log('兑换奖品:', prize, '剩余积分:', points);
// 处理兑换逻辑
}}
customRedeem={(prize, defaultCallback) => {
// 自定义兑换逻辑,比如显示确认弹窗
const confirmed = window.confirm(`确定要兑换 ${prize.name} 吗?`);
if (confirmed) {
return defaultCallback();
}
}}
loadingText="加载中..."
emptyText="暂无奖品"
/>
```
### AwardItemView
奖励item组件,显示单个奖励项。支持不同兑换状态的显示。
```tsx
import { AwardItemView } from '@lofter-mission/react';
<AwardItemView
prize={prize}
customRedeem={(prize, defaultCallback) => {
// 自定义兑换前的确认逻辑
if (window.confirm(`消耗 ${prize.prizeBizConfigDTO?.consumables?.prizeNum} 积分兑换?`)) {
return defaultCallback();
}
}}
redeemText="立即兑换"
cardClassName="custom-award-card"
/>
```
## 完整使用示例
### 基础使用
```tsx
import React, { useState } from 'react';
import {
ActivityContainer,
ActivityUser,
MissionListView,
MissionPopupView,
AwardListView,
Mission,
PrizeItem
} from '@lofter-mission/react';
const App: React.FC = () => {
const [showPopup, setShowPopup] = useState(false);
const handleMissionProgress = (mission: Mission, points?: number) => {
console.log('任务进度更新:', mission);
console.log('当前积分:', points);
};
const handlePrizeRedeem = (prize: PrizeItem, points?: number) => {
console.log('兑换成功:', prize);
console.log('剩余积分:', points);
};
const handleCustomShare = (mission: Mission) => {
// 处理分享任务的自定义逻辑
console.log('执行分享任务:', mission);
// 例如:调用原生分享API或显示分享弹窗
};
return (
<ActivityContainer activityCode="confession">
<div style={{ padding: '20px' }}>
<h1>告白活动</h1>
{/* 用户信息 */}
<ActivityUser pointsText="我的积分" />
{/* 任务列表 */}
<MissionListView
onMissionProgressUpdate={handleMissionProgress}
onCustomShare={handleCustomShare}
/>
{/* 奖励列表 */}
<AwardListView
onPrizeRedeem={handlePrizeRedeem}
/>
{/* 弹窗触发按钮 */}
<button onClick={() => setShowPopup(true)}>
打开任务弹窗
</button>
{/* 任务弹窗 */}
<MissionPopupView
visible={showPopup}
onClose={() => setShowPopup(false)}
onMissionProgressUpdate={handleMissionProgress}
title="任务中心"
/>
</div>
</ActivityContainer>
);
};
export default App;
```
### 高级使用
#### 使用 Context Hook
```tsx
import React from 'react';
import { useActivity } from '@lofter-mission/react';
const CustomComponent: React.FC = () => {
const {
activityInfo,
missions,
userInfo,
missionApi,
loading,
error,
refreshActivity,
updateMissionProgress
} = useActivity();
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
const handleRefresh = async () => {
try {
// silent=true 表示静默刷新,不显示loading状态
const result = await refreshActivity(true);
console.log('刷新结果:', result);
} catch (error) {
console.error('刷新失败:', error);
}
};
return (
<div>
<h2>{activityInfo?.name}</h2>
<p>任务总数: {missions.length}</p>
<p>用户积分: {userInfo?.points}</p>
<button onClick={handleRefresh}>静默刷新</button>
{/* 显示不同标签的任务 */}
{missions.map(mission => (
<div key={mission.info.code}>
<span>{mission.info.title}</span>
<span>标签: {mission.tabName}</span>
<span>状态: {mission.status === 1 ? '已完成' : mission.status === 0 ? '可领取' : '进行中'}</span>
</div>
))}
</div>
);
};
```
#### 自定义兑换逻辑
```tsx
import React from 'react';
import { AwardItemView, PrizeItem } from '@lofter-mission/react';
const CustomAwardItem: React.FC<{ prize: PrizeItem }> = ({ prize }) => {
const handleCustomRedeem = async (
prize: PrizeItem,
defaultCallback: () => Promise<{ status: boolean; points?: number }>
) => {
// 显示自定义确认弹窗
const costInfo = prize.prizeBizConfigDTO?.consumables;
const confirmMessage = `确定要消耗 ${costInfo?.prizeNum} ${costInfo?.prizeName} 兑换 ${prize.name} 吗?`;
if (window.confirm(confirmMessage)) {
try {
const result = await defaultCallback();
if (result.status) {
alert(`兑换成功!剩余积分: ${result.points}`);
}
} catch (error) {
alert('兑换失败,请重试');
}
}
};
return (
<AwardItemView
prize={prize}
customRedeem={handleCustomRedeem}
cardClassName="my-award-card"
buttonClassName="my-award-button"
/>
);
};
```
#### 筛选特定标签任务
```tsx
// 只显示每日任务
<MissionListView
tabName="daily"
onMissionProgressUpdate={handleMissionProgress}
/>
// 只显示新手任务
<MissionListView
tabName="newbie"
onMissionProgressUpdate={handleMissionProgress}
/>
```
## TypeScript 支持
组件库完全支持 TypeScript,提供完整的类型定义:
```tsx
import {
Mission,
ActivityInfo,
UserInfo,
PrizeItem,
AwardActivity,
MissionItemViewProps,
ActivityContainerProps,
BaseStyleProps
} from '@lofter-mission/react';
// Mission 类型基于 @lofter-mission/core 的 MissionItem 类
const mission: Mission = {
info: {
code: 'daily_signin',
title: '每日签到',
// ... 其他字段
},
status: -1, // -1: 进行中, 0: 可领取, 1: 已完成
executeTask: () => Promise<any>,
receiveAward: () => Promise<any>,
// ... 其他方法和属性
};
// 用户信息类型
const userInfo: UserInfo = {
points: 100,
level: 1,
nickname: '用户昵称'
};
// 奖品类型
const prize: PrizeItem = {
itemCode: 'prize001',
name: '精美礼品',
image: '/prize.jpg',
userStatus: 0, // 0: 可兑换, -1: 已抢光, -2: 已兑换
prizeBizConfigDTO: {
consumables: {
prizeNum: 100,
prizeName: '积分'
}
}
};
```
## 样式自定义
组件库提供了基础样式,你可以通过 CSS 类名进行自定义:
```css
/* 自定义任务项样式 */
.mission-item-card {
border: 2px solid #your-color;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.mission-content {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
.mission-title {
font-size: 14px;
font-weight: 500;
color: #333;
}
.mission-reward {
display: flex;
align-items: center;
gap: 4px;
}
.action-button {
padding: 8px 16px;
border-radius: 20px;
border: none;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
color: white;
cursor: pointer;
}
.action-button.completed {
background: #ccc;
cursor: not-allowed;
}
/* 自定义弹窗样式 */
.mission-popup {
max-width: 600px;
border-radius: 12px;
}
/* 自定义用户信息样式 */
.activity-user {
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
padding: 20px;
border-radius: 12px;
color: white;
}
/* 自定义奖励项样式 */
.award-item-card {
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
.award-content {
padding: 16px;
text-align: center;
}
.award-button.available {
background: #4CAF50;
color: white;
}
.award-button.unavailable {
background: #ccc;
color: #666;
cursor: not-allowed;
}
```
## API 参考
### 组件 Props
#### ActivityContainerProps
```tsx
interface ActivityContainerProps extends BaseStyleProps {
activityCode: string; // 活动代码,必填
children?: ReactNode;
presetMissionApiService?: MissionApiService; // 预设的API服务实例,避免重复初始化
}
```
#### ActivityUserProps
```tsx
interface ActivityUserProps extends BaseStyleProps {
activityInfo?: ActivityInfo;
loadingClassName?: string;
errorClassName?: string;
noDataClassName?: string;
loadingText?: string;
errorText?: string;
noDataText?: string;
pointsText?: string; // 积分显示文本
}
```
#### MissionItemViewProps
```tsx
interface MissionItemViewProps extends BaseStyleProps {
activityInfo?: ActivityInfo;
mission: Mission; // 任务对象,基于 MissionItem 类
onProgressUpdate?: (mission: Mission, points?: number) => void; // 任务进度更新回调
onCustomShare?: (mission: Mission) => void; // 自定义分享处理
cardClassName?: string;
titleClassName?: string;
rewardClassName?: string;
actionClassName?: string;
buttonClassName?: string;
completedText?: string; // 已完成状态文本
defaultActionText?: string; // 默认行动按钮文本
}
```
#### MissionListViewProps
```tsx
interface MissionListViewProps extends BaseStyleProps {
activityInfo?: ActivityInfo;
tabName?: string; // 筛选特定标签的任务
onMissionProgressUpdate?: (mission: Mission, points?: number) => void;
onCustomShare?: (mission: Mission) => void;
loadingClassName?: string;
errorClassName?: string;
emptyClassName?: string;
itemsClassName?: string;
loadingText?: string;
errorText?: string;
emptyText?: string;
}
```
#### MissionPopupViewProps
```tsx
interface MissionPopupViewProps extends BaseStyleProps {
activityInfo?: ActivityInfo;
visible?: boolean; // 是否显示弹窗
onClose?: () => void; // 关闭弹窗回调
onMissionProgressUpdate?: (mission: Mission) => void;
overlayClassName?: string; // 遮罩层样式类名
popupClassName?: string; // 弹窗样式类名
headerClassName?: string;
contentClassName?: string;
closeButtonClassName?: string;
title?: string; // 弹窗标题
loadingText?: string;
errorText?: string;
retryText?: string;
}
```
#### AwardItemViewProps
```tsx
interface AwardItemViewProps extends BaseStyleProps {
activityInfo?: ActivityInfo;
prize: PrizeItem; // 奖品对象
customRedeem?: (
prize: PrizeItem,
callback: () => Promise<{ status: boolean; points?: number }>
) => Promise<void>; // 自定义兑换逻辑
cardClassName?: string;
imageClassName?: string;
titleClassName?: string;
buttonClassName?: string;
redeemText?: string; // 兑换按钮文本
}
```
#### AwardListViewProps
```tsx
interface AwardListViewProps extends BaseStyleProps {
activityInfo?: ActivityInfo;
onPrizeRedeem?: (prize: PrizeItem, points?: number) => void; // 兑换成功回调
customRedeem?: (
prize: PrizeItem,
callback: () => Promise<{ status: boolean; points?: number }>
) => Promise<void>;
loadingClassName?: string;
errorClassName?: string;
emptyClassName?: string;
itemsClassName?: string;
loadingText?: string;
errorText?: string;
emptyText?: string;
}
```
### Context Hook
#### useActivity
```tsx
interface ActivityContextType {
activityInfo: ActivityInfo | null; // 活动信息
missions: Mission[]; // 任务列表
userInfo: UserInfo | null; // 用户信息
missionApi: MissionApiService | null; // API服务实例
loading: boolean; // 加载状态
error: string | null; // 错误信息
refreshActivity: (silent?: boolean) => Promise<{
userInfo: UserInfo | null;
activityInfo: ActivityInfo | null;
missions: Mission[]
}>; // 刷新活动数据
updateMissionProgress: (mission: Mission) => void; // 更新任务进度
}
```
### 核心类型
#### Mission
```tsx
// 基于 @lofter-mission/core 的 MissionItem 类,包含以下关键属性:
interface Mission extends MissionItem {
tabName?: string; // 任务所属标签
status: number; // -1: 进行中, 0: 可领取, 1: 已完成
info: {
code: string;
title: string;
actionCopyWriting?: string;
showMissionType?: number; // 任务显示类型,2表示分享任务
awardNum?: number; // 奖励数量
awardName?: string; // 奖励名称
awardIcon?: string; // 奖励图标
currentProgress?: number;
totalProgress?: number;
invitedAvatarList?: string[]; // 邀请任务的头像列表
// ... 其他字段
};
executeTask: () => Promise<any>; // 执行任务方法
receiveAward: () => Promise<any>; // 领取奖励方法
progressText?: string; // 进度文本
}
```
## 常见问题
### Q: 如何处理不同类型的任务?
A: 组件会根据任务的 `showMissionType` 自动处理不同类型的任务。对于分享任务(`showMissionType === 2`),你可以通过 `onCustomShare` 回调来自定义分享逻辑。
### Q: 如何自定义兑换确认弹窗?
A: 使用 `customRedeem` 属性来实现自定义兑换逻辑:
```tsx
<AwardItemView
prize={prize}
customRedeem={async (prize, defaultCallback) => {
if (window.confirm('确定兑换吗?')) {
return await defaultCallback();
}
}}
/>
```
### Q: 如何避免重复初始化 MissionApiService?
A: 在 `ActivityContainer` 中传入 `presetMissionApiService` 属性:
```tsx
const missionApi = new MissionApiService({ activityCode: 'your-code' });
<ActivityContainer
activityCode="your-code"
presetMissionApiService={missionApi}
>
{/* 组件内容 */}
</ActivityContainer>
```
## 开发和构建
```bash
# 安装依赖
npm install
# 开发模式
npm run dev
# 构建
npm run build
# 测试
npm test
```
## 许可证
MIT
## 贡献
欢迎提交 Issue 和 Pull Request!