UNPKG

@lofter-mission/react

Version:

React 组件库,基于 @lofter-mission/core 实现活动任务相关组件

684 lines (578 loc) 16.6 kB
# @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!