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

780 lines
21 KiB
Markdown

# OneApp Membership - 会员系统模块文档
## 模块概述
`oneapp_membership` 是 OneApp 的会员系统核心模块,专注于积分系统和签到功能。该模块提供用户积分管理、签到中心、积分任务等功能,与社区、账户等模块深度集成。
### 基本信息
- **模块名称**: oneapp_membership
- **版本**: 0.0.1
- **类型**: Flutter Package
- **主要功能**: 积分系统、签到功能、积分任务管理
### 核心导出组件
```dart
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)
基于真实项目代码的用户积分管理页面:
```dart
/// 用户积分页面
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. 会员权益管理
#### 权益服务系统
```dart
// 会员权益服务
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. 会员商城
#### 积分商城服务
```dart
// 积分商城服务
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;
}
```
## 页面组件设计
### 会员主页
```dart
// 会员主页
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();
},
);
}
}
```
### 会员卡片组件
```dart
// 会员卡片组件
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),
),
],
);
}
}
```
## 状态管理
### 会员状态管理
```dart
// 会员状态 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());
},
);
}
}
```
## 与其他模块集成
### 社区集成
```dart
// 会员社区权益服务
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**: 微信支付集成(会员升级付费)
## 错误处理
### 会员系统异常
```dart
// 会员系统异常
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 构建了完整的会员生态体系,通过等级管理、积分系统、权益服务和商城功能,提升了用户粘性和付费转化。模块与社区、账户、售后服务等模块深度集成,为用户提供了全方位的会员体验和价值认知。