# @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！
