Files
oneapp_docs/membership
2025-09-24 14:08:54 +08:00
..
2025-09-24 14:08:54 +08:00

OneApp Membership - 会员系统模块文档

模块概述

oneapp_membership 是 OneApp 的会员系统核心模块,专注于积分系统和签到功能。该模块提供用户积分管理、签到中心、积分任务等功能,与社区、账户等模块深度集成。

基本信息

  • 模块名称: oneapp_membership
  • 版本: 0.0.1
  • 类型: Flutter Package
  • 主要功能: 积分系统、签到功能、积分任务管理

核心导出组件

library oneapp_membership;

// 积分页面
export 'src/app_modules/app_points/pages/user_points_page.dart';
// 签到中心页面
export 'src/app_modules/app_signin/pages/signIn_center_page.dart';
// 会员事件
export 'src/app_event/membership_event.dart';

目录结构

oneapp_membership/
├── lib/
│   ├── oneapp_membership.dart        # 主导出文件
│   ├── generated/                    # 生成的国际化文件
│   ├── l10n/                        # 国际化资源文件
│   └── src/                         # 源代码目录
│       ├── app_modules/             # 应用模块
│       │   ├── app_points/          # 积分模块
│       │   │   ├── pages/           # 积分页面
│       │   │   ├── blocs/           # 积分状态管理
│       │   │   ├── widges/          # 积分组件
│       │   │   ├── scene/           # 积分场景
│       │   │   └── beans/           # 积分数据模型
│       │   └── app_signin/          # 签到模块
│       │       └── pages/           # 签到页面
│       ├── app_net_service/         # 网络服务
│       └── app_event/               # 应用事件
├── assets/                          # 静态资源
├── test/                           # 测试文件
└── pubspec.yaml                    # 依赖配置

核心功能模块

1. 用户积分页面 (UserPointsPage)

基于真实项目代码的用户积分管理页面:

/// 用户积分页面
class UserPointsPage extends BaseStatefulWidget with RouteObjProvider {
  UserPointsPage({Key? key, required this.userId});

  final String userId;

  @override
  BaseStatefulWidgetState<UserPointsPage> getState() => _UserPointsPageState();
}

class _UserPointsPageState extends BaseStatefulWidgetState<UserPointsPage>
    with WidgetsBindingObserver {
  
  /// 是否需要添加推送积分
  bool needAddPushPoints = false;
  
  /// 是否使用通用导航栏
  bool useCommonNavigation = false;

  @override
  List<Widget> get rightActions => [
    PointsRuleWidge(
      lable: MemberShipIntlDelegate.current.pointsRule,
      onTap: () async {
        // 获取积分规则
        final rsp3 = await UserPointsTask.getRule(ruleKey: "integrationRule");
        if ((rsp3.data ?? '').isNotEmpty) {
          String jumpUrl = 'oneapp://component?routeKey=Key_Community_Postdetail&postId=${rsp3.data}&userName=';
          
          try {
            Uri uri = Uri.tryParse(jumpUrl)!;
            final routeKey = uri.queryParameters['routeKey'];
            final meta = RouteCenterAPI.routeMetaBy(routeKey!);
            NavigatorProxy().launchMeta(
              meta,
              meta.routePath,
              arguments: uri.queryParameters,
            );
          } catch (e) {
            // 处理跳转异常
          }
        } else {
          ToastHelper.showToast(msg: '获取文章失败');
        }
      }
    )
  ];

  @override
  String get titleText => MemberShipIntlDelegate.current.myPoints;

  @override
  double get elevation => 0;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    addNotifyPoints();
  }

  /// 添加通知积分
  Future<void> addNotifyPoints() async {
    bool haveNotifyPermission = await Permission.notification.isGranted;
    if (haveNotifyPermission) {
      PointsAddCenter().addPoints(PointsOpenPush());
    }
  }
}

2. 积分组件架构

实际项目包含的积分相关组件:

  • point_score_head_section.dart: 积分头部区域组件
  • points_rule_widget.dart: 积分规则组件
  • points_task_cell.dart: 积分任务单元格组件
  • PointsAddCenter: 积分添加中心
  • points_open_push.dart: 积分推送开启

3. 会员权益管理

权益服务系统

// 会员权益服务
class MemberBenefitsService {
  final BenefitsRepository _repository;
  final MembershipService _membershipService;
  
  MemberBenefitsService(this._repository, this._membershipService);
  
  // 获取用户可用权益
  Future<Result<List<MemberBenefit>>> getUserBenefits(String userId) async {
    try {
      final membership = await _membershipService.getUserMembership(userId);
      return membership.fold(
        (failure) => Left(BenefitsFailure.membershipNotFound()),
        (membershipInfo) async {
          final benefits = await _repository.getBenefitsForTier(
            membershipInfo.tier,
          );
          return Right(benefits);
        },
      );
    } catch (e) {
      return Left(BenefitsFailure.loadFailed(e.toString()));
    }
  }
  
  // 使用权益
  Future<Result<BenefitUsage>> useBenefit({
    required String userId,
    required String benefitId,
    Map<String, dynamic>? context,
  }) async {
    try {
      // 检查权益可用性
      final benefit = await _repository.getBenefitById(benefitId);
      if (benefit == null) {
        return Left(BenefitsFailure.benefitNotFound());
      }
      
      // 检查使用条件
      final canUseResult = await _canUseBenefit(userId, benefit);
      if (canUseResult.isLeft()) {
        return Left(canUseResult.fold((l) => l, (r) => throw Exception()));
      }
      
      // 记录使用
      final usage = BenefitUsage(
        id: generateId(),
        userId: userId,
        benefitId: benefitId,
        usedAt: DateTime.now(),
        context: context,
      );
      
      await _repository.recordBenefitUsage(usage);
      
      // 执行权益逻辑
      await _executeBenefitLogic(benefit, usage);
      
      return Right(usage);
    } catch (e) {
      return Left(BenefitsFailure.usageFailed(e.toString()));
    }
  }
  
  // 获取权益使用历史
  Future<Result<List<BenefitUsage>>> getBenefitUsageHistory({
    required String userId,
    String? benefitId,
    DateRange? dateRange,
    int page = 1,
    int pageSize = 20,
  }) async {
    try {
      final usageHistory = await _repository.getBenefitUsageHistory(
        userId: userId,
        benefitId: benefitId,
        dateRange: dateRange,
        page: page,
        pageSize: pageSize,
      );
      return Right(usageHistory);
    } catch (e) {
      return Left(BenefitsFailure.historyLoadFailed(e.toString()));
    }
  }
}

// 会员权益模型
class MemberBenefit {
  final String id;
  final String name;
  final String description;
  final BenefitType type;
  final BenefitCategory category;
  final Map<String, dynamic> configuration;
  final UsageLimit usageLimit;
  final List<MembershipTier> eligibleTiers;
  final DateTime? validFrom;
  final DateTime? validUntil;
  final bool isActive;
  
  const MemberBenefit({
    required this.id,
    required this.name,
    required this.description,
    required this.type,
    required this.category,
    required this.configuration,
    required this.usageLimit,
    required this.eligibleTiers,
    this.validFrom,
    this.validUntil,
    this.isActive = true,
  });
  
  // 免费充电权益
  factory MemberBenefit.freeCharging({int times = 1}) {
    return MemberBenefit(
      id: 'free_charging',
      name: '免费充电',
      description: '每月享受 $times 次免费充电服务',
      type: BenefitType.service,
      category: BenefitCategory.charging,
      configuration: {'free_times': times},
      usageLimit: UsageLimit.monthly(times),
      eligibleTiers: [
        MembershipTier.gold,
        MembershipTier.platinum,
        MembershipTier.diamond,
        MembershipTier.vip,
      ],
    );
  }
  
  // 优先客服权益
  factory MemberBenefit.prioritySupport() {
    return MemberBenefit(
      id: 'priority_support',
      name: '优先客服',
      description: '享受7x24小时优先客服支持',
      type: BenefitType.service,
      category: BenefitCategory.support,
      configuration: {'priority_level': 'high'},
      usageLimit: UsageLimit.unlimited(),
      eligibleTiers: [
        MembershipTier.gold,
        MembershipTier.platinum,
        MembershipTier.diamond,
        MembershipTier.vip,
      ],
    );
  }
}

4. 会员商城

积分商城服务

// 积分商城服务
class MembershipStoreService {
  final StoreRepository _repository;
  final PointsService _pointsService;
  final OrderService _orderService;
  
  MembershipStoreService(
    this._repository,
    this._pointsService,
    this._orderService,
  );
  
  // 获取商品列表
  Future<Result<List<StoreItem>>> getStoreItems({
    StoreCategory? category,
    MembershipTier? minTier,
    int page = 1,
    int pageSize = 20,
  }) async {
    try {
      final items = await _repository.getStoreItems(
        category: category,
        minTier: minTier,
        page: page,
        pageSize: pageSize,
      );
      return Right(items);
    } catch (e) {
      return Left(StoreFailure.loadFailed(e.toString()));
    }
  }
  
  // 兑换商品
  Future<Result<StoreOrder>> redeemItem({
    required String userId,
    required String itemId,
    int quantity = 1,
    String? deliveryAddress,
  }) async {
    try {
      // 获取商品信息
      final item = await _repository.getStoreItemById(itemId);
      if (item == null) {
        return Left(StoreFailure.itemNotFound());
      }
      
      // 检查积分余额
      final totalCost = item.pointsPrice * quantity;
      final pointsResult = await _pointsService.getUserPoints(userId);
      
      return pointsResult.fold(
        (failure) => Left(StoreFailure.pointsCheckFailed()),
        (balance) async {
          if (balance.availablePoints < totalCost) {
            return Left(StoreFailure.insufficientPoints());
          }
          
          // 检查库存
          if (item.stock != null && item.stock! < quantity) {
            return Left(StoreFailure.insufficientStock());
          }
          
          // 创建订单
          final order = StoreOrder(
            id: generateId(),
            userId: userId,
            itemId: itemId,
            itemName: item.name,
            quantity: quantity,
            pointsPrice: item.pointsPrice,
            totalPoints: totalCost,
            deliveryAddress: deliveryAddress,
            status: StoreOrderStatus.pending,
            createdAt: DateTime.now(),
          );
          
          // 扣除积分
          await _pointsService.spendPoints(
            userId: userId,
            points: totalCost,
            reason: PointsSpendReason.storeRedemption,
            metadata: {'order_id': order.id, 'item_id': itemId},
          );
          
          // 保存订单
          await _repository.createStoreOrder(order);
          
          // 更新库存
          if (item.stock != null) {
            await _repository.updateItemStock(itemId, -quantity);
          }
          
          return Right(order);
        },
      );
    } catch (e) {
      return Left(StoreFailure.redeemFailed(e.toString()));
    }
  }
  
  // 获取兑换记录
  Future<Result<List<StoreOrder>>> getRedemptionHistory({
    required String userId,
    StoreOrderStatus? status,
    int page = 1,
    int pageSize = 20,
  }) async {
    try {
      final orders = await _repository.getStoreOrders(
        userId: userId,
        status: status,
        page: page,
        pageSize: pageSize,
      );
      return Right(orders);
    } catch (e) {
      return Left(StoreFailure.historyLoadFailed(e.toString()));
    }
  }
}

// 商城商品模型
class StoreItem {
  final String id;
  final String name;
  final String description;
  final String imageUrl;
  final int pointsPrice;
  final StoreCategory category;
  final MembershipTier? requiredTier;
  final int? stock;
  final bool isVirtual;
  final Map<String, dynamic>? metadata;
  final DateTime? validUntil;
  final bool isActive;
  
  const StoreItem({
    required this.id,
    required this.name,
    required this.description,
    required this.imageUrl,
    required this.pointsPrice,
    required this.category,
    this.requiredTier,
    this.stock,
    this.isVirtual = false,
    this.metadata,
    this.validUntil,
    this.isActive = true,
  });
  
  bool get isInStock => stock == null || stock! > 0;
  bool get isExpired => validUntil != null && validUntil!.isBefore(DateTime.now());
  bool get isAvailable => isActive && isInStock && !isExpired;
}

页面组件设计

会员主页

// 会员主页
class MembershipHomePage extends StatefulWidget {
  @override
  _MembershipHomePageState createState() => _MembershipHomePageState();
}

class _MembershipHomePageState extends State<MembershipHomePage> {
  late MembershipBloc _membershipBloc;
  late PointsBloc _pointsBloc;
  
  @override
  void initState() {
    super.initState();
    _membershipBloc = context.read<MembershipBloc>();
    _pointsBloc = context.read<PointsBloc>();
    
    _membershipBloc.add(LoadMembershipInfo());
    _pointsBloc.add(LoadPointsBalance());
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          _buildAppBar(),
          SliverToBoxAdapter(child: _buildMembershipCard()),
          SliverToBoxAdapter(child: _buildPointsSection()),
          SliverToBoxAdapter(child: _buildBenefitsSection()),
          SliverToBoxAdapter(child: _buildQuickActions()),
          SliverToBoxAdapter(child: _buildPromotions()),
        ],
      ),
    );
  }
  
  Widget _buildMembershipCard() {
    return BlocBuilder<MembershipBloc, MembershipState>(
      builder: (context, state) {
        if (state is MembershipLoaded) {
          return MembershipCard(
            membershipInfo: state.membershipInfo,
            onUpgrade: () => _navigateToUpgrade(),
          );
        }
        return MembershipCardSkeleton();
      },
    );
  }
  
  Widget _buildPointsSection() {
    return BlocBuilder<PointsBloc, PointsState>(
      builder: (context, state) {
        if (state is PointsLoaded) {
          return PointsOverviewCard(
            balance: state.balance,
            onViewHistory: () => _navigateToPointsHistory(),
            onEarnMore: () => _navigateToEarnPoints(),
          );
        }
        return PointsCardSkeleton();
      },
    );
  }
}

会员卡片组件

// 会员卡片组件
class MembershipCard extends StatelessWidget {
  final MembershipInfo membershipInfo;
  final VoidCallback? onUpgrade;
  
  const MembershipCard({
    Key? key,
    required this.membershipInfo,
    this.onUpgrade,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(16),
      padding: EdgeInsets.all(20),
      decoration: BoxDecoration(
        gradient: _getGradientForTier(membershipInfo.tier),
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            blurRadius: 10,
            offset: Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildHeader(),
          SizedBox(height: 16),
          _buildProgress(),
          SizedBox(height: 16),
          _buildActions(),
        ],
      ),
    );
  }
  
  Widget _buildHeader() {
    return Row(
      children: [
        CircleAvatar(
          radius: 24,
          backgroundColor: Colors.white.withOpacity(0.2),
          child: Icon(
            _getIconForTier(membershipInfo.tier),
            color: Colors.white,
            size: 28,
          ),
        ),
        SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                membershipInfo.tierName,
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: Colors.white,
                ),
              ),
              Text(
                '会员编号: ${membershipInfo.memberNumber}',
                style: TextStyle(
                  fontSize: 14,
                  color: Colors.white.withOpacity(0.8),
                ),
              ),
            ],
          ),
        ),
        if (membershipInfo.tier != MembershipTier.vip)
          TextButton(
            onPressed: onUpgrade,
            child: Text(
              '升级',
              style: TextStyle(color: Colors.white),
            ),
            style: TextButton.styleFrom(
              backgroundColor: Colors.white.withOpacity(0.2),
            ),
          ),
      ],
    );
  }
  
  Widget _buildProgress() {
    if (membershipInfo.tier == MembershipTier.vip) {
      return Container(); // VIP 不显示进度
    }
    
    final nextTier = _getNextTier(membershipInfo.tier);
    final currentPoints = membershipInfo.totalPoints;
    final requiredPoints = _getRequiredPointsForTier(nextTier);
    final progress = currentPoints / requiredPoints;
    
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '距离${_getTierName(nextTier)}还需 ${requiredPoints - currentPoints} 积分',
          style: TextStyle(
            color: Colors.white.withOpacity(0.9),
            fontSize: 14,
          ),
        ),
        SizedBox(height: 8),
        LinearProgressIndicator(
          value: progress.clamp(0.0, 1.0),
          backgroundColor: Colors.white.withOpacity(0.3),
          valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
        ),
      ],
    );
  }
}

状态管理

会员状态管理

// 会员状态 BLoC
class MembershipBloc extends Bloc<MembershipEvent, MembershipState> {
  final MembershipService _membershipService;
  final PointsService _pointsService;
  
  MembershipBloc(this._membershipService, this._pointsService) 
      : super(MembershipInitial()) {
    on<LoadMembershipInfo>(_onLoadMembershipInfo);
    on<UpgradeMembership>(_onUpgradeMembership);
    on<RefreshMembershipInfo>(_onRefreshMembershipInfo);
  }
  
  Future<void> _onLoadMembershipInfo(
    LoadMembershipInfo event,
    Emitter<MembershipState> emit,
  ) async {
    emit(MembershipLoading());
    
    final result = await _membershipService.getUserMembership(event.userId);
    result.fold(
      (failure) => emit(MembershipError(failure.message)),
      (membershipInfo) => emit(MembershipLoaded(membershipInfo)),
    );
  }
  
  Future<void> _onUpgradeMembership(
    UpgradeMembership event,
    Emitter<MembershipState> emit,
  ) async {
    emit(MembershipUpgrading());
    
    final result = await _membershipService.upgradeMembership(
      userId: event.userId,
      targetTier: event.targetTier,
      paymentMethod: event.paymentMethod,
    );
    
    result.fold(
      (failure) => emit(MembershipError(failure.message)),
      (_) {
        add(LoadMembershipInfo(event.userId));
        emit(MembershipUpgradeSuccess());
      },
    );
  }
}

与其他模块集成

社区集成

// 会员社区权益服务
class MembershipCommunityIntegration {
  final MembershipService _membershipService;
  final CommunityService _communityService;
  
  MembershipCommunityIntegration(
    this._membershipService,
    this._communityService,
  );
  
  // 检查发布权限
  Future<bool> canPublishPremiumContent(String userId) async {
    final membership = await _membershipService.getUserMembership(userId);
    return membership.fold(
      (_) => false,
      (info) => info.tier.index >= MembershipTier.gold.index,
    );
  }
  
  // 获取会员专属话题
  Future<List<Topic>> getMemberExclusiveTopics(String userId) async {
    final membership = await _membershipService.getUserMembership(userId);
    return membership.fold(
      (_) => [],
      (info) => _communityService.getTopicsForMemberTier(info.tier),
    );
  }
}

依赖管理

集成模块依赖

  • basic_utils: 基础工具类
  • basic_uis: 基础 UI 组件
  • oneapp_community: 社区功能集成
  • oneapp_after_sales: 售后服务集成
  • app_account: 账户系统集成

第三方依赖

  • fluwx: 微信支付集成(会员升级付费)

错误处理

会员系统异常

// 会员系统异常
abstract class MembershipFailure {
  const MembershipFailure();
  
  factory MembershipFailure.loadFailed(String message) = LoadFailure;
  factory MembershipFailure.upgradeFailed(String message) = UpgradeFailure;
  factory MembershipFailure.paymentFailed() = PaymentFailure;
  factory MembershipFailure.upgradeNotAllowed() = UpgradeNotAllowedFailure;
  factory MembershipFailure.insufficientPoints() = InsufficientPointsFailure;
}

// 积分系统异常
abstract class PointsFailure {
  const PointsFailure();
  
  factory PointsFailure.insufficientPoints() = InsufficientPointsFailure;
  factory PointsFailure.earnFailed(String message) = EarnFailure;
  factory PointsFailure.spendFailed(String message) = SpendFailure;
}

总结

oneapp_membership 模块为 OneApp 构建了完整的会员生态体系,通过等级管理、积分系统、权益服务和商城功能,提升了用户粘性和付费转化。模块与社区、账户、售后服务等模块深度集成,为用户提供了全方位的会员体验和价值认知。