Flutter+HarmonyOS跨端实战—第11篇:积分系统与广告集成
·
实现应用的商业化模式
前言
积分系统是 CleanMark AI 的核心商业模式。用户通过积分消费 AI 服务,可以通过以下方式获取积分:
- 注册赠送:新用户注册赠送 5 积分
- 观看广告:观看激励视频广告获得 1 积分
- 充值购买:通过内购或支付宝/微信支付购买积分包
这篇文章我会带你实现完整的积分系统和广告集成。
一、积分系统设计
1.1 业务规则
| 操作 | 积分变化 | 说明 |
|---|---|---|
| 注册 | +5 | 新用户注册赠送 |
| 图片去水印 | -1 | 每次处理消耗 1 积分 |
| 视频去水印 | -3 | 每次处理消耗 3 积分 |
| 观看广告 | +1 | 每天最多 10 次 |
| 充值 | +N | 根据充值金额 |
![]() |
1.2 数据模型
// lib/features/credits/models/credit_transaction.dart
/// 积分交易记录
class CreditTransaction {
final String id;
final String userId;
final int amount; // 积分变化量(正数为增加,负数为减少)
final TransactionType type; // 交易类型
final String? description; // 描述
final DateTime createdAt;
const CreditTransaction({
required this.id,
required this.userId,
required this.amount,
required this.type,
this.description,
required this.createdAt,
});
factory CreditTransaction.fromJson(Map<String, dynamic> json) {
return CreditTransaction(
id: json['id'] as String,
userId: json['userId'] as String,
amount: json['amount'] as int,
type: TransactionType.fromString(json['type'] as String),
description: json['description'] as String?,
createdAt: DateTime.parse(json['createdAt'] as String),
);
}
}
/// 交易类型
enum TransactionType {
register, // 注册赠送
consume, // 消费
adReward, // 广告奖励
purchase; // 充值购买
static TransactionType fromString(String type) {
switch (type.toUpperCase()) {
case 'REGISTER':
return TransactionType.register;
case 'CONSUME':
return TransactionType.consume;
case 'AD_REWARD':
return TransactionType.adReward;
case 'PURCHASE':
return TransactionType.purchase;
default:
return TransactionType.consume;
}
}
String get displayName {
switch (this) {
case TransactionType.register:
return '注册赠送';
case TransactionType.consume:
return '消费';
case TransactionType.adReward:
return '广告奖励';
case TransactionType.purchase:
return '充值购买';
}
}
}
1.3 积分充值套餐
// lib/features/credits/models/credit_package.dart
/// 积分充值套餐
class CreditPackage {
final String id;
final int credits; // 积分数量
final double price; // 价格(元)
final String? bonus; // 赠送说明
final bool isPopular; // 是否热门
const CreditPackage({
required this.id,
required this.credits,
required this.price,
this.bonus,
this.isPopular = false,
});
/// 单价(元/积分)
double get unitPrice => price / credits;
}
/// 预定义套餐
const List<CreditPackage> creditPackages = [
CreditPackage(
id: 'package_10',
credits: 10,
price: 6.0,
),
CreditPackage(
id: 'package_50',
credits: 50,
price: 25.0,
bonus: '赠送 5 积分',
isPopular: true,
),
CreditPackage(
id: 'package_100',
credits: 100,
price: 45.0,
bonus: '赠送 15 积分',
),
CreditPackage(
id: 'package_500',
credits: 500,
price: 200.0,
bonus: '赠送 100 积分',
),
];
二、积分展示组件

2.1 积分余额卡片
// lib/features/credits/presentation/widgets/credit_balance_card.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app/app_colors.dart';
import '../../../auth/presentation/providers/user_provider.dart';
class CreditBalanceCard extends ConsumerWidget {
const CreditBalanceCard({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userProvider);
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColors.purple,
AppColors.blue,
],
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColors.purple.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'我的积分',
style: TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
IconButton(
icon: const Icon(Icons.history, color: Colors.white70),
onPressed: () {
Navigator.pushNamed(context, '/credit-history');
},
),
],
),
const SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${user?.credits ?? 0}',
style: const TextStyle(
color: Colors.white,
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 8),
const Padding(
padding: EdgeInsets.only(bottom: 12),
child: Text(
'积分',
style: TextStyle(
color: Colors.white70,
fontSize: 16,
),
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildActionButton(
icon: Icons.add_circle_outline,
label: '充值',
onTap: () {
Navigator.pushNamed(context, '/credit-purchase');
},
),
),
const SizedBox(width: 12),
Expanded(
child: _buildActionButton(
icon: Icons.play_circle_outline,
label: '看广告赚积分',
onTap: () {
// 显示广告
},
),
),
],
),
],
),
);
}
Widget _buildActionButton({
required IconData icon,
required String label,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Colors.white, size: 20),
const SizedBox(width: 8),
Text(
label,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
}
三、积分充值页面
3.1 充值页面 UI
// lib/features/credits/presentation/pages/credit_purchase_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app/app_colors.dart';
import '../../../../shared/widgets/gradient_button.dart';
import '../../models/credit_package.dart';
class CreditPurchasePage extends ConsumerStatefulWidget {
const CreditPurchasePage({super.key});
ConsumerState<CreditPurchasePage> createState() => _CreditPurchasePageState();
}
class _CreditPurchasePageState extends ConsumerState<CreditPurchasePage> {
CreditPackage? _selectedPackage;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('充值积分'),
),
body: Column(
children: [
Expanded(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
// 套餐列表
...creditPackages.map((package) => _buildPackageCard(package)),
const SizedBox(height: 24),
// 说明
_buildDescription(),
],
),
),
// 底部购买按钮
_buildBottomBar(),
],
),
);
}
Widget _buildPackageCard(CreditPackage package) {
final isSelected = _selectedPackage?.id == package.id;
return GestureDetector(
onTap: () {
setState(() {
_selectedPackage = package;
});
},
child: Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.cardDark,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected ? AppColors.purple : AppColors.gray700,
width: 2,
),
),
child: Row(
children: [
// 选择指示器
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? AppColors.purple : AppColors.gray500,
width: 2,
),
color: isSelected ? AppColors.purple : Colors.transparent,
),
child: isSelected
? const Icon(Icons.check, size: 16, color: Colors.white)
: null,
),
const SizedBox(width: 16),
// 积分信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'${package.credits} 积分',
style: const TextStyle(
color: AppColors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
if (package.isPopular) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: AppColors.orange,
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'热门',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
],
],
),
if (package.bonus != null) ...[
const SizedBox(height: 4),
Text(
package.bonus!,
style: const TextStyle(
color: AppColors.orange,
fontSize: 12,
),
),
],
],
),
),
// 价格
Text(
'¥${package.price.toStringAsFixed(2)}',
style: const TextStyle(
color: AppColors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
Widget _buildDescription() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppColors.blue.withOpacity(0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.info_outline, color: AppColors.blue, size: 20),
const SizedBox(width: 8),
const Text(
'积分说明',
style: TextStyle(
color: AppColors.blue,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 8),
Text(
'• 图片去水印消耗 1 积分\n'
'• 视频去水印消耗 3 积分\n'
'• 积分永久有效,不会过期\n'
'• 支持支付宝、微信支付',
style: TextStyle(
color: AppColors.gray400,
fontSize: 14,
height: 1.5,
),
),
],
),
);
}
Widget _buildBottomBar() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.cardDark,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, -5),
),
],
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_selectedPackage != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'支付金额',
style: TextStyle(
color: AppColors.gray400,
fontSize: 14,
),
),
Text(
'¥${_selectedPackage!.price.toStringAsFixed(2)}',
style: const TextStyle(
color: AppColors.orange,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
GradientButton(
text: _selectedPackage == null ? '请选择套餐' : '立即支付',
onPressed: _selectedPackage == null ? null : _handlePurchase,
),
],
),
),
);
}
Future<void> _handlePurchase() async {
if (_selectedPackage == null) return;
// 显示支付方式选择
final paymentMethod = await _showPaymentMethodDialog();
if (paymentMethod == null) return;
// 调用支付
// TODO: 实现支付逻辑
}
Future<String?> _showPaymentMethodDialog() async {
return showDialog<String>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: AppColors.cardDark,
title: const Text(
'选择支付方式',
style: TextStyle(color: AppColors.white),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildPaymentOption('支付宝', Icons.account_balance_wallet, 'alipay'),
const SizedBox(height: 12),
_buildPaymentOption('微信支付', Icons.wechat, 'wechat'),
],
),
),
);
}
Widget _buildPaymentOption(String label, IconData icon, String method) {
return InkWell(
onTap: () => Navigator.pop(context, method),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.gray800,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(icon, color: AppColors.white),
const SizedBox(width: 16),
Text(
label,
style: const TextStyle(
color: AppColors.white,
fontSize: 16,
),
),
],
),
),
);
}
}
四、广告集成
4.1 Google AdMob 集成
添加依赖:
# pubspec.yaml
dependencies:
google_mobile_ads: ^5.0.0
配置广告 ID:
// lib/core/constants/ad_constants.dart
class AdConstants {
// Android 广告 ID
static const String androidRewardedAdId = 'ca-app-pub-xxxxx/xxxxx';
// iOS 广告 ID
static const String iosRewardedAdId = 'ca-app-pub-xxxxx/xxxxx';
// 测试广告 ID
static const String testRewardedAdId = 'ca-app-pub-3940256099942544/5224354917';
/// 获取激励视频广告 ID
static String get rewardedAdId {
if (kDebugMode) {
return testRewardedAdId;
}
return PlatformUtils.isAndroid ? androidRewardedAdId : iosRewardedAdId;
}
}
4.2 广告服务封装
// lib/core/services/ad_service.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/foundation.dart';
import '../constants/ad_constants.dart';
class AdService {
static final AdService _instance = AdService._internal();
factory AdService() => _instance;
AdService._internal();
RewardedAd? _rewardedAd;
bool _isAdLoading = false;
bool _isAdReady = false;
/// 初始化广告 SDK
static Future<void> initialize() async {
await MobileAds.instance.initialize();
}
/// 加载激励视频广告
Future<void> loadRewardedAd() async {
if (_isAdLoading || _isAdReady) return;
_isAdLoading = true;
await RewardedAd.load(
adUnitId: AdConstants.rewardedAdId,
request: const AdRequest(),
rewardedAdLoadCallback: RewardedAdLoadCallback(
onAdLoaded: (ad) {
_rewardedAd = ad;
_isAdReady = true;
_isAdLoading = false;
// 设置全屏内容回调
_rewardedAd!.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
_rewardedAd = null;
_isAdReady = false;
// 预加载下一个广告
loadRewardedAd();
},
onAdFailedToShowFullScreenContent: (ad, error) {
ad.dispose();
_rewardedAd = null;
_isAdReady = false;
},
);
},
onAdFailedToLoad: (error) {
_isAdLoading = false;
_isAdReady = false;
if (kDebugMode) {
print('激励视频广告加载失败: ${error.message}');
}
},
),
);
}
/// 显示激励视频广告
Future<bool> showRewardedAd() async {
if (!_isAdReady || _rewardedAd == null) {
return false;
}
bool rewarded = false;
await _rewardedAd!.show(
onUserEarnedReward: (ad, reward) {
rewarded = true;
if (kDebugMode) {
print('用户获得奖励: ${reward.amount} ${reward.type}');
}
},
);
return rewarded;
}
/// 广告是否已准备好
bool get isAdReady => _isAdReady;
/// 释放资源
void dispose() {
_rewardedAd?.dispose();
_rewardedAd = null;
_isAdReady = false;
}
}
4.3 观看广告赚积分

// lib/features/credits/presentation/widgets/watch_ad_button.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app/app_colors.dart';
import '../../../../core/services/ad_service.dart';
import '../providers/credits_provider.dart';
class WatchAdButton extends ConsumerStatefulWidget {
const WatchAdButton({super.key});
ConsumerState<WatchAdButton> createState() => _WatchAdButtonState();
}
class _WatchAdButtonState extends ConsumerState<WatchAdButton> {
final AdService _adService = AdService();
bool _isLoading = false;
void initState() {
super.initState();
// 预加载广告
_adService.loadRewardedAd();
}
Future<void> _watchAd() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
try {
// 检查今日观看次数
final canWatch = await ref.read(creditsProvider.notifier).canWatchAd();
if (!canWatch) {
_showMessage('今日观看次数已达上限(10次)');
return;
}
// 显示广告
final rewarded = await _adService.showRewardedAd();
if (rewarded) {
// 发放奖励
final result = await ref.read(creditsProvider.notifier).rewardAdCredit();
result.fold(
(failure) => _showMessage(failure.message),
(credits) => _showMessage('恭喜获得 1 积分!当前积分:$credits'),
);
} else {
_showMessage('广告未完整观看,未获得奖励');
}
} finally {
setState(() {
_isLoading = false;
});
}
}
void _showMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
Widget build(BuildContext context) {
return ElevatedButton.icon(
onPressed: _isLoading ? null : _watchAd,
icon: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.play_circle_outline),
label: Text(_isLoading ? '加载中...' : '看广告赚 1 积分'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.orange,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
);
}
}
4.4 广告次数限制
// lib/features/credits/presentation/providers/credits_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dartz/dartz.dart';
import '../../../../core/errors/failures.dart';
import '../../domain/usecases/reward_ad_credit_usecase.dart';
import '../../domain/usecases/check_ad_limit_usecase.dart';
class CreditsNotifier extends StateNotifier<int> {
final RewardAdCreditUseCase _rewardAdCreditUseCase;
final CheckAdLimitUseCase _checkAdLimitUseCase;
CreditsNotifier(
this._rewardAdCreditUseCase,
this._checkAdLimitUseCase,
) : super(0);
/// 检查是否可以观看广告
Future<bool> canWatchAd() async {
final result = await _checkAdLimitUseCase();
return result.fold(
(failure) => false,
(canWatch) => canWatch,
);
}
/// 发放广告奖励
Future<Either<Failure, int>> rewardAdCredit() async {
final result = await _rewardAdCreditUseCase();
return result.fold(
(failure) => Left(failure),
(newCredits) {
state = newCredits;
return Right(newCredits);
},
);
}
}
final creditsProvider = StateNotifierProvider<CreditsNotifier, int>(
(ref) {
final rewardAdCreditUseCase = ref.watch(rewardAdCreditUseCaseProvider);
final checkAdLimitUseCase = ref.watch(checkAdLimitUseCaseProvider);
return CreditsNotifier(rewardAdCreditUseCase, checkAdLimitUseCase);
},
);
五、积分历史记录
5.1 历史记录页面
// lib/features/credits/presentation/pages/credit_history_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../app/app_colors.dart';
import '../../models/credit_transaction.dart';
import '../providers/credit_history_provider.dart';
class CreditHistoryPage extends ConsumerWidget {
const CreditHistoryPage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final historyState = ref.watch(creditHistoryProvider);
return Scaffold(
appBar: AppBar(
title: const Text('积分明细'),
),
body: historyState.when(
data: (transactions) {
if (transactions.isEmpty) {
return _buildEmptyState();
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: transactions.length,
itemBuilder: (context, index) {
return _buildTransactionItem(transactions[index]);
},
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Text(
'加载失败: $error',
style: const TextStyle(color: AppColors.red),
),
),
),
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.receipt_long_outlined,
size: 64,
color: AppColors.gray500,
),
const SizedBox(height: 16),
Text(
'暂无积分记录',
style: TextStyle(
color: AppColors.gray400,
fontSize: 16,
),
),
],
),
);
}
Widget _buildTransactionItem(CreditTransaction transaction) {
final isIncome = transaction.amount > 0;
final color = isIncome ? AppColors.green : AppColors.red;
final sign = isIncome ? '+' : '';
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.cardDark,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
// 图标
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
_getIconForType(transaction.type),
color: color,
size: 20,
),
),
const SizedBox(width: 12),
// 信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
transaction.type.displayName,
style: const TextStyle(
color: AppColors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
_formatDateTime(transaction.createdAt),
style: const TextStyle(
color: AppColors.gray400,
fontSize: 12,
),
),
],
),
),
// 积分变化
Text(
'$sign${transaction.amount}',
style: TextStyle(
color: color,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
IconData _getIconForType(TransactionType type) {
switch (type) {
case TransactionType.register:
return Icons.card_giftcard;
case TransactionType.consume:
return Icons.remove_circle_outline;
case TransactionType.adReward:
return Icons.play_circle_outline;
case TransactionType.purchase:
return Icons.add_circle_outline;
}
}
String _formatDateTime(DateTime dateTime) {
return '${dateTime.month}-${dateTime.day} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
}
}
本篇完整小结
这篇文章我们完成了:
- ✅ 积分系统业务规则设计
- ✅ 数据模型定义(CreditTransaction、CreditPackage)
- ✅ 积分余额卡片组件
- ✅ 积分充值页面(套餐选择 + 支付)
- ✅ Google AdMob 集成
- ✅ 激励视频广告实现
- ✅ 观看广告赚积分功能
- ✅ 广告次数限制(每天 10 次)
- ✅ 积分历史记录页面
关键要点:
- 积分是应用的核心商业模式,贯穿整个用户流程
- 使用 Google AdMob 提供免费获取积分的途径
- 充值套餐设计要有梯度,引导用户购买更大套餐
- 广告次数限制防止滥用,保护广告收益
- 积分历史记录提供透明度,增强用户信任
广告集成最佳实践:
- 预加载广告:在用户点击前提前加载,减少等待时间
- 测试广告 ID:开发阶段使用测试 ID,避免账号被封
- 错误处理:广告加载失败时提供友好提示
- 奖励验证:服务器端验证广告观看,防止作弊
- 用户体验:不要过度打扰用户,合理控制广告频率
HarmonyOS 适配注意事项:
- Google AdMob 不支持 HarmonyOS,需要集成华为广告服务(Petal Ads)
- 使用条件编译或平台判断,为不同平台提供不同广告实现
- 封装统一的广告接口,隔离平台差异
// 平台适配示例
abstract class AdService {
Future<void> loadRewardedAd();
Future<bool> showRewardedAd();
}
class GoogleAdService implements AdService {
// Google AdMob 实现
}
class HuaweiAdService implements AdService {
// 华为广告实现
}
AdService createAdService() {
if (PlatformUtils.isHarmonyOS) {
return HuaweiAdService();
}
return GoogleAdService();
}
思考题
- 如何防止用户通过修改系统时间绕过广告次数限制?
- 如果用户观看广告后应用崩溃,如何确保积分正确发放?
- 如何设计积分套餐定价策略,最大化收益?
下一篇预告:第12篇 - 历史记录与任务管理
更多推荐



所有评论(0)