873 lines
26 KiB
Markdown
873 lines
26 KiB
Markdown
# Genex Flutter 移动端开发指南
|
||
|
||
> Consumer App(消费者端)+ Merchant App(商户核销端)
|
||
|
||
---
|
||
|
||
## 1. 技术栈概览
|
||
|
||
| 技术 | 版本 | 用途 |
|
||
|------|------|------|
|
||
| **Flutter** | 3.x | 跨平台UI框架 |
|
||
| **Dart** | 3.x | 开发语言 |
|
||
| **Riverpod** | 2.x | 状态管理(Provider替代方案,编译安全) |
|
||
| **GoRouter** | 最新 | 声明式路由、深链接 |
|
||
| **Dio** | 5.x | HTTP客户端 |
|
||
| **Freezed** | 最新 | 不可变数据模型代码生成 |
|
||
| **Hive/Isar** | 最新 | 本地持久化 |
|
||
| **WebSocket** | 内置 | AI Agent实时通信、行情推送 |
|
||
| **flutter_localizations** | 内置 | 国际化 |
|
||
|
||
---
|
||
|
||
## 2. 项目架构:Clean Architecture + Riverpod
|
||
|
||
### 2.1 目录结构
|
||
|
||
```
|
||
genex_mobile/
|
||
├── lib/
|
||
│ ├── main.dart # 应用入口
|
||
│ ├── app/
|
||
│ │ ├── app.dart # MaterialApp配置
|
||
│ │ ├── router.dart # GoRouter路由定义
|
||
│ │ └── theme/ # 主题配置
|
||
│ ├── core/
|
||
│ │ ├── constants/ # 常量(API地址、术语映射表)
|
||
│ │ ├── network/ # Dio配置、拦截器、错误处理
|
||
│ │ ├── storage/ # 本地存储封装
|
||
│ │ ├── utils/ # 工具类
|
||
│ │ └── extensions/ # Dart扩展方法
|
||
│ ├── features/ # 按功能模块组织
|
||
│ │ ├── auth/ # 注册/登录
|
||
│ │ │ ├── data/
|
||
│ │ │ │ ├── datasources/ # 远程/本地数据源
|
||
│ │ │ │ ├── models/ # DTO(Freezed生成)
|
||
│ │ │ │ └── repositories/ # Repository实现
|
||
│ │ │ ├── domain/
|
||
│ │ │ │ ├── entities/ # 领域实体
|
||
│ │ │ │ ├── repositories/ # Repository接口
|
||
│ │ │ │ └── usecases/ # 用例
|
||
│ │ │ └── presentation/
|
||
│ │ │ ├── providers/ # Riverpod Providers
|
||
│ │ │ ├── pages/ # 页面Widget
|
||
│ │ │ └── widgets/ # 模块专用组件
|
||
│ │ ├── coupons/ # 券浏览/购买/持有
|
||
│ │ ├── trading/ # 二级市场交易
|
||
│ │ ├── wallet/ # 余额/资产/交易记录
|
||
│ │ ├── redeem/ # 券使用/核销
|
||
│ │ ├── transfer/ # P2P转赠
|
||
│ │ ├── profile/ # 个人中心/KYC
|
||
│ │ ├── ai_agent/ # AI Agent对话/建议
|
||
│ │ └── merchant/ # 商户端核销(共享模块)
|
||
│ ├── shared/
|
||
│ │ ├── widgets/ # 全局共享组件
|
||
│ │ ├── providers/ # 全局Provider
|
||
│ │ └── models/ # 共享数据模型
|
||
│ └── l10n/ # 国际化资源
|
||
├── test/ # 单元/Widget测试
|
||
├── integration_test/ # 集成测试
|
||
└── pubspec.yaml
|
||
```
|
||
|
||
### 2.2 Clean Architecture 分层
|
||
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ Presentation Layer │
|
||
│ Pages → Widgets → Riverpod Providers │
|
||
├────────────────────────────────────────┤
|
||
│ Domain Layer │
|
||
│ Entities → UseCases → Repository(接口)│
|
||
├────────────────────────────────────────┤
|
||
│ Data Layer │
|
||
│ Models(DTO) → DataSources → Repo实现 │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
**依赖方向**:Presentation → Domain ← Data(Domain层不依赖任何外层)
|
||
|
||
---
|
||
|
||
## 3. 核心模块实现
|
||
|
||
### 3.1 术语映射(全局执行)
|
||
|
||
> 所有面向用户的UI文本必须使用Web2术语,禁止出现区块链术语。
|
||
|
||
```dart
|
||
// lib/core/constants/terminology.dart
|
||
class Terminology {
|
||
// 用户界面术语 → 底层技术术语
|
||
static const Map<String, String> mapping = {
|
||
'我的账户': '链上钱包地址',
|
||
'我的券': 'ERC-721/1155 NFT资产',
|
||
'我的余额': '链上稳定币(USDC)余额',
|
||
'转赠给朋友': 'P2P链上转移',
|
||
'购买': '链上原子交换',
|
||
'核销/使用': '合约兑付(Redemption)',
|
||
'订单号': '交易哈希(TX Hash)',
|
||
'安全验证': '链上签名(MPC钱包后台执行)',
|
||
};
|
||
}
|
||
```
|
||
|
||
### 3.2 认证模块
|
||
|
||
```dart
|
||
// lib/features/auth/domain/entities/user.dart
|
||
@freezed
|
||
class User with _$User {
|
||
const factory User({
|
||
required String id,
|
||
required String phone, // 或 email
|
||
required KycLevel kycLevel, // L0/L1/L2/L3
|
||
required WalletMode walletMode, // standard/pro
|
||
String? displayName,
|
||
String? avatar,
|
||
}) = _User;
|
||
}
|
||
|
||
enum KycLevel { L0, L1, L2, L3 }
|
||
enum WalletMode { standard, pro }
|
||
```
|
||
|
||
```dart
|
||
// lib/features/auth/domain/usecases/register_usecase.dart
|
||
class RegisterUseCase {
|
||
final AuthRepository _repo;
|
||
RegisterUseCase(this._repo);
|
||
|
||
/// 注册:手机号/邮箱 → 后台自动创建MPC钱包,用户无感知
|
||
Future<Either<Failure, User>> call(RegisterParams params) {
|
||
return _repo.register(
|
||
phone: params.phone,
|
||
email: params.email,
|
||
password: params.password,
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.3 券资产模块
|
||
|
||
```dart
|
||
// lib/features/coupons/domain/entities/coupon.dart
|
||
@freezed
|
||
class Coupon with _$Coupon {
|
||
const factory Coupon({
|
||
required String id, // 券ID(链上唯一)
|
||
required String issuerName, // 发行方名称
|
||
required double faceValue, // 面值
|
||
required double currentPrice, // 当前市场价
|
||
required DateTime expiryDate, // 到期日期
|
||
required CouponStatus status, // 可用/已使用/已过期
|
||
required CouponType type, // utility/security
|
||
String? imageUrl,
|
||
String? description,
|
||
List<String>? usageConditions,
|
||
}) = _Coupon;
|
||
}
|
||
|
||
enum CouponStatus { available, used, expired, listed }
|
||
enum CouponType { utility, security }
|
||
```
|
||
|
||
```dart
|
||
// lib/features/coupons/presentation/providers/coupon_providers.dart
|
||
final couponListProvider = FutureProvider.autoDispose<List<Coupon>>((ref) {
|
||
final repo = ref.watch(couponRepositoryProvider);
|
||
return repo.getMyCoupons();
|
||
});
|
||
|
||
final couponDetailProvider = FutureProvider.autoDispose.family<Coupon, String>(
|
||
(ref, couponId) {
|
||
final repo = ref.watch(couponRepositoryProvider);
|
||
return repo.getCouponDetail(couponId);
|
||
},
|
||
);
|
||
```
|
||
|
||
### 3.4 交易模块
|
||
|
||
```dart
|
||
// lib/features/trading/domain/entities/order.dart
|
||
@freezed
|
||
class TradeOrder with _$TradeOrder {
|
||
const factory TradeOrder({
|
||
required String orderId, // 订单号(映射TX Hash)
|
||
required String couponId,
|
||
required OrderSide side, // buy/sell
|
||
required double price,
|
||
required OrderStatus status,
|
||
required DateTime createdAt,
|
||
}) = _TradeOrder;
|
||
}
|
||
|
||
enum OrderSide { buy, sell }
|
||
enum OrderStatus { pending, matched, settled, cancelled }
|
||
```
|
||
|
||
**Utility Track价格校验(前端 + 后端双重验证)**:
|
||
|
||
```dart
|
||
// lib/features/trading/presentation/providers/sell_provider.dart
|
||
class SellNotifier extends StateNotifier<SellState> {
|
||
// Utility Track券:卖出价 ≤ 面值
|
||
bool validatePrice(double sellPrice, double faceValue, CouponType type) {
|
||
if (type == CouponType.utility && sellPrice > faceValue) {
|
||
return false; // 消费型券不允许溢价
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.5 P2P转赠
|
||
|
||
```dart
|
||
// 转赠流程:输入朋友手机号 → 翻译层解析为链上地址 → 链上P2P转移
|
||
class TransferUseCase {
|
||
final TransferRepository _repo;
|
||
TransferUseCase(this._repo);
|
||
|
||
Future<Either<Failure, TransferResult>> call({
|
||
required String couponId,
|
||
required String recipientPhone, // 手机号(非链上地址)
|
||
}) {
|
||
return _repo.transferByPhone(
|
||
couponId: couponId,
|
||
recipientPhone: recipientPhone,
|
||
);
|
||
// 后端翻译层:手机号 → 链上地址 → Gas代付 → 链上P2P转移
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. AI Agent 集成
|
||
|
||
### 4.1 架构
|
||
|
||
```
|
||
┌──────────────────────────────┐
|
||
│ AI Agent UI Layer │
|
||
│ 悬浮按钮 / 对话面板 / 建议条 │
|
||
├──────────────────────────────┤
|
||
│ AI Agent SDK │
|
||
│ 对话管理 / 上下文组装 / 流式 │
|
||
├──────────────────────────────┤
|
||
│ WebSocket连接 │
|
||
│ Agent Gateway API │
|
||
└──────────────────────────────┘
|
||
```
|
||
|
||
### 4.2 悬浮入口按钮
|
||
|
||
```dart
|
||
// lib/features/ai_agent/presentation/widgets/ai_fab.dart
|
||
class AiAgentFab extends ConsumerWidget {
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
final unreadCount = ref.watch(aiUnreadCountProvider);
|
||
return Positioned(
|
||
right: 16,
|
||
bottom: 80,
|
||
child: GestureDetector(
|
||
onTap: () => _showAgentPanel(context),
|
||
onLongPress: () => _showQuickActions(context),
|
||
child: Stack(
|
||
children: [
|
||
CircleAvatar(
|
||
radius: 28,
|
||
child: Icon(Icons.smart_toy_outlined),
|
||
),
|
||
if (unreadCount > 0)
|
||
Positioned(
|
||
right: 0, top: 0,
|
||
child: Badge(count: unreadCount),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3 对话面板(流式输出)
|
||
|
||
```dart
|
||
// lib/features/ai_agent/presentation/pages/agent_chat_panel.dart
|
||
class AgentChatPanel extends ConsumerStatefulWidget {
|
||
@override
|
||
_AgentChatPanelState createState() => _AgentChatPanelState();
|
||
}
|
||
|
||
class _AgentChatPanelState extends ConsumerState<AgentChatPanel> {
|
||
final _channel = WebSocketChannel.connect(
|
||
Uri.parse('wss://api.gogenex.com/agent/ws'),
|
||
);
|
||
|
||
void _sendMessage(String text) {
|
||
final context = _buildContext(); // 组装上下文
|
||
_channel.sink.add(jsonEncode({
|
||
'type': 'chat',
|
||
'message': text,
|
||
'context': context,
|
||
}));
|
||
}
|
||
|
||
Map<String, dynamic> _buildContext() {
|
||
return {
|
||
'user_profile': ref.read(userProfileProvider).toJson(),
|
||
'current_page': GoRouter.of(context).location,
|
||
'coupon_portfolio': ref.read(myCouponsProvider).toSummary(),
|
||
'recent_actions': ref.read(recentActionsProvider),
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4 消费者端AI场景
|
||
|
||
| 场景 | 实现方式 |
|
||
|------|---------|
|
||
| 智能选券推荐 | 首页Feed中插入AI推荐卡片 |
|
||
| 价格顾问 | 券详情页底部AI分析标签 |
|
||
| 到期管理 | 我的券列表AI排序+推送提醒 |
|
||
| 出售定价 | 出售页面AI建议价格+解释 |
|
||
| 自然语言搜索 | 对话面板返回筛选券卡片 |
|
||
|
||
---
|
||
|
||
## 5. 商户端核销模块
|
||
|
||
### 5.1 核销方式
|
||
|
||
| 方式 | 场景 | 技术 |
|
||
|------|------|------|
|
||
| 扫码核销 | 门店扫消费者券码 | Camera → QR解码 → API调用 |
|
||
| 输码核销 | 手动输入券码 | 文本输入 → API调用 |
|
||
| 离线核销 | 网络不可用 | 本地验证 → 队列缓存 → 联网同步 |
|
||
|
||
### 5.2 离线核销实现
|
||
|
||
```dart
|
||
// lib/features/merchant/data/services/offline_redeem_service.dart
|
||
class OfflineRedeemService {
|
||
final _queue = HiveBox<PendingRedemption>('offline_queue');
|
||
final _localValidator = LocalCouponValidator();
|
||
|
||
/// 离线核销:本地验证 + 缓存 + 联网自动同步
|
||
Future<RedeemResult> redeemOffline(String couponCode) async {
|
||
// 1. 本地验证(预下载的有效券列表 + 签名验证)
|
||
final isValid = await _localValidator.validate(couponCode);
|
||
if (!isValid) return RedeemResult.invalid();
|
||
|
||
// 2. 本地标记已核销,加入同步队列
|
||
await _queue.add(PendingRedemption(
|
||
couponCode: couponCode,
|
||
timestamp: DateTime.now(),
|
||
storeId: currentStoreId,
|
||
));
|
||
|
||
return RedeemResult.pendingSync();
|
||
}
|
||
|
||
/// 联网后自动同步
|
||
Future<void> syncPendingRedemptions() async {
|
||
final pending = _queue.values.toList();
|
||
for (final item in pending) {
|
||
try {
|
||
await _api.confirmRedemption(item);
|
||
await _queue.delete(item.key);
|
||
} catch (_) {
|
||
// 重试逻辑
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 网络层
|
||
|
||
### 6.1 Dio配置
|
||
|
||
```dart
|
||
// lib/core/network/api_client.dart
|
||
class ApiClient {
|
||
late final Dio _dio;
|
||
|
||
ApiClient() {
|
||
_dio = Dio(BaseOptions(
|
||
baseUrl: 'https://api.gogenex.com/api/v1',
|
||
connectTimeout: Duration(seconds: 10),
|
||
receiveTimeout: Duration(seconds: 30),
|
||
));
|
||
|
||
_dio.interceptors.addAll([
|
||
AuthInterceptor(), // JWT Token注入
|
||
LoggingInterceptor(), // 日志
|
||
RetryInterceptor(), // 重试策略
|
||
ErrorInterceptor(), // 统一错误处理
|
||
]);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.2 错误处理
|
||
|
||
```dart
|
||
// lib/core/network/failure.dart
|
||
@freezed
|
||
class Failure with _$Failure {
|
||
const factory Failure.network({String? message}) = NetworkFailure;
|
||
const factory Failure.server({required int code, String? message}) = ServerFailure;
|
||
const factory Failure.auth({String? message}) = AuthFailure;
|
||
const factory Failure.validation({required Map<String, String> errors}) = ValidationFailure;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 状态管理模式(Riverpod)
|
||
|
||
```dart
|
||
// 只读数据查询
|
||
final couponListProvider = FutureProvider.autoDispose<List<Coupon>>((ref) async {
|
||
return ref.watch(couponRepoProvider).getAll();
|
||
});
|
||
|
||
// 可变状态管理
|
||
final cartProvider = StateNotifierProvider<CartNotifier, CartState>((ref) {
|
||
return CartNotifier(ref.watch(orderRepoProvider));
|
||
});
|
||
|
||
// 异步操作
|
||
final purchaseProvider = FutureProvider.family<Order, PurchaseParams>((ref, params) {
|
||
return ref.watch(orderRepoProvider).purchase(params);
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 应用构建配置
|
||
|
||
### 8.1 多环境配置
|
||
|
||
```dart
|
||
// lib/core/config/env.dart
|
||
enum Environment { dev, staging, prod }
|
||
|
||
class AppConfig {
|
||
static late Environment env;
|
||
static String get apiBase => switch (env) {
|
||
Environment.dev => 'https://dev-api.gogenex.com',
|
||
Environment.staging => 'https://staging-api.gogenex.com',
|
||
Environment.prod => 'https://api.gogenex.com',
|
||
};
|
||
}
|
||
```
|
||
|
||
### 8.2 Flavor构建
|
||
|
||
```yaml
|
||
# Consumer App
|
||
flutter run --flavor consumer -t lib/main_consumer.dart
|
||
|
||
# Merchant App
|
||
flutter run --flavor merchant -t lib/main_merchant.dart
|
||
```
|
||
|
||
Consumer和Merchant共享核心模块(`core/`、`shared/`),通过不同入口和路由配置区分功能。
|
||
|
||
---
|
||
|
||
## 9. 测试策略
|
||
|
||
| 层级 | 工具 | 覆盖目标 |
|
||
|------|------|---------|
|
||
| 单元测试 | `flutter_test` + `mocktail` | UseCase、Repository、Provider |
|
||
| Widget测试 | `flutter_test` | 关键页面组件 |
|
||
| 集成测试 | `integration_test` | 购买流程、核销流程、转赠流程 |
|
||
| Golden测试 | `golden_toolkit` | UI快照回归 |
|
||
|
||
```dart
|
||
// test/features/trading/sell_notifier_test.dart
|
||
void main() {
|
||
test('Utility券不允许溢价出售', () {
|
||
final notifier = SellNotifier();
|
||
expect(
|
||
notifier.validatePrice(110.0, 100.0, CouponType.utility),
|
||
false, // 110 > 面值100,拒绝
|
||
);
|
||
expect(
|
||
notifier.validatePrice(85.0, 100.0, CouponType.utility),
|
||
true, // 85 < 面值100,允许
|
||
);
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 性能优化
|
||
|
||
| 策略 | 实现 |
|
||
|------|------|
|
||
| 图片懒加载 | `cached_network_image` + CDN |
|
||
| 列表虚拟化 | `ListView.builder` + 分页加载 |
|
||
| 离线缓存 | Hive本地数据库 + 增量同步 |
|
||
| 启动优化 | 延迟初始化非核心服务 |
|
||
| 包体积 | Tree-shaking + 延迟加载 |
|
||
| 内存管理 | `autoDispose` Provider自动释放 |
|
||
|
||
---
|
||
|
||
## 11. 安全规范
|
||
|
||
- JWT Token存储于Flutter Secure Storage(非SharedPreferences)
|
||
- 敏感操作(大额交易、转赠)需二次验证(PIN/生物识别)
|
||
- API通信全链路HTTPS,Certificate Pinning
|
||
- 本地数据加密存储(Hive加密Box)
|
||
- 禁止在日志中输出敏感信息(Token、用户手机号)
|
||
- ProGuard/R8混淆(Android)
|
||
|
||
---
|
||
|
||
## 12. 发布流程
|
||
|
||
```
|
||
开发 → 提交PR → CI自动测试 → Code Review → 合入main
|
||
↓
|
||
自动构建(GitHub Actions / GitLab CI)
|
||
↓
|
||
┌────────────────┼────────────────┐
|
||
↓ ↓ ↓
|
||
Dev Build Staging Build Prod Build
|
||
(内部测试) (TestFlight/Beta) (App Store/Play Store)
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Pro模式(加密原生用户)
|
||
|
||
### 13.1 Pro模式切换
|
||
|
||
```dart
|
||
// lib/features/profile/presentation/pages/pro_mode_settings.dart
|
||
class ProModeSettings extends ConsumerWidget {
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
final user = ref.watch(userProfileProvider);
|
||
|
||
return Column(children: [
|
||
// 风险提示确认
|
||
RiskDisclosureCard(
|
||
text: '切换至Pro模式后,您将自行管理钱包私钥。'
|
||
'平台无法帮您恢复丢失的私钥或资产。',
|
||
),
|
||
|
||
// 开启Pro模式
|
||
SwitchListTile(
|
||
title: Text('开启Pro模式'),
|
||
value: user.walletMode == WalletMode.pro,
|
||
onChanged: (enabled) async {
|
||
if (enabled) {
|
||
// 必须确认风险提示
|
||
final confirmed = await showRiskConfirmDialog(context);
|
||
if (confirmed) {
|
||
await ref.read(walletProvider.notifier).switchToProMode();
|
||
}
|
||
} else {
|
||
// Pro→标准:需将资产转回平台托管
|
||
await ref.read(walletProvider.notifier).switchToStandard();
|
||
}
|
||
},
|
||
),
|
||
|
||
if (user.walletMode == WalletMode.pro) ...[
|
||
// 链上地址显示
|
||
CopyableAddressField(address: user.chainAddress),
|
||
// WalletConnect连接
|
||
WalletConnectButton(),
|
||
// MetaMask连接
|
||
MetaMaskConnectButton(),
|
||
// 交易哈希查看入口
|
||
ListTile(
|
||
title: Text('查看链上交易'),
|
||
onTap: () => launchUrl(explorerUrl),
|
||
),
|
||
],
|
||
]);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 13.2 助记词备份与社交恢复
|
||
|
||
```dart
|
||
// lib/features/wallet/domain/usecases/backup_usecase.dart
|
||
class BackupSeedPhraseUseCase {
|
||
/// Pro模式开启时强制提示备份助记词
|
||
Future<Either<Failure, void>> call() async {
|
||
final mnemonic = await _walletService.generateMnemonic();
|
||
// 显示12词助记词,用户手动抄写
|
||
// 验证确认(随机抽3个词验证)
|
||
return _walletService.confirmBackup(mnemonic);
|
||
}
|
||
}
|
||
|
||
// 社交恢复(Guardian机制)
|
||
class SocialRecoveryUseCase {
|
||
/// 预设3-5个可信联系人,多数确认即可恢复
|
||
Future<Either<Failure, void>> setupGuardians(List<String> guardianPhones) async {
|
||
if (guardianPhones.length < 3 || guardianPhones.length > 5) {
|
||
return Left(Failure.validation(errors: {'guardians': '需要3-5个守护人'}));
|
||
}
|
||
return _repo.setupSocialRecovery(guardianPhones);
|
||
}
|
||
|
||
/// 发起恢复(需>50%守护人确认)
|
||
Future<Either<Failure, void>> initiateRecovery() async {
|
||
return _repo.initiateRecovery();
|
||
}
|
||
}
|
||
|
||
// AA钱包(ERC-4337)集成
|
||
class AAWalletService {
|
||
/// 邮箱/手机号作为恢复入口
|
||
Future<void> recoverViaEmail(String email) async {
|
||
// ERC-4337 Account Abstraction恢复流程
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 14. 提取到外部钱包
|
||
|
||
```dart
|
||
// lib/features/wallet/presentation/pages/extract_to_external.dart
|
||
class ExtractToExternalPage extends ConsumerWidget {
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
return Column(children: [
|
||
// KYC L2+才可提取
|
||
KycGuard(requiredLevel: KycLevel.L2, child: Column(children: [
|
||
// 输入外部钱包地址(EVM兼容)
|
||
TextField(
|
||
decoration: InputDecoration(labelText: '外部钱包地址(0x...)'),
|
||
controller: _addressController,
|
||
),
|
||
// 风险提示
|
||
WarningCard(
|
||
text: '提取后平台将不再托管该券。'
|
||
'您需自行保管钱包私钥,平台无法冻结、恢复或干预该券。',
|
||
),
|
||
// 选择要提取的券
|
||
CouponSelector(onSelected: (coupons) => setState(() => _selected = coupons)),
|
||
// 确认提取
|
||
ElevatedButton(
|
||
onPressed: () => _confirmExtract(ref),
|
||
child: Text('确认提取'),
|
||
),
|
||
])),
|
||
]);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 15. 个人中心完整模块
|
||
|
||
### 15.1 转赠记录
|
||
|
||
```dart
|
||
// lib/features/transfer/presentation/pages/transfer_history.dart
|
||
class TransferHistoryPage extends ConsumerWidget {
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
final transfers = ref.watch(transferHistoryProvider);
|
||
|
||
return transfers.when(
|
||
data: (list) => ListView.builder(
|
||
itemCount: list.length,
|
||
itemBuilder: (_, i) => TransferHistoryCard(
|
||
direction: list[i].direction, // sent / received
|
||
recipientDisplay: list[i].recipientPhone ?? '外部钱包',
|
||
couponName: list[i].couponName,
|
||
timestamp: list[i].createdAt,
|
||
status: list[i].status, // completed / pending
|
||
),
|
||
),
|
||
loading: () => SkeletonList(),
|
||
error: (e, _) => ErrorWidget(e),
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 15.2 我的余额/充值/提现
|
||
|
||
```dart
|
||
// lib/features/wallet/domain/entities/balance.dart
|
||
@freezed
|
||
class WalletBalance with _$WalletBalance {
|
||
const factory WalletBalance({
|
||
required double totalBalance, // 总余额(链上+链下聚合,美元显示)
|
||
required double availableBalance, // 可用余额
|
||
required double frozenBalance, // 冻结金额(挂单中/待结算)
|
||
required double withdrawable, // 可提现
|
||
}) = _WalletBalance;
|
||
}
|
||
|
||
// 充值/提现流程
|
||
class DepositUseCase {
|
||
Future<Either<Failure, DepositResult>> call(DepositParams params) {
|
||
// 银行卡/信用卡/Apple Pay → 法币入金 → 后台转换USDC
|
||
return _repo.deposit(params);
|
||
}
|
||
}
|
||
|
||
class WithdrawUseCase {
|
||
Future<Either<Failure, WithdrawResult>> call(WithdrawParams params) {
|
||
// USDC → 法币 → 银行卡(T+1到账)
|
||
return _repo.withdraw(params);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 15.3 我的订单
|
||
|
||
```dart
|
||
// lib/features/trading/presentation/pages/order_history.dart
|
||
class OrderHistoryPage extends ConsumerWidget {
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
final orders = ref.watch(orderHistoryProvider);
|
||
return DefaultTabController(
|
||
length: 3,
|
||
child: Column(children: [
|
||
TabBar(tabs: [
|
||
Tab(text: '全部'),
|
||
Tab(text: '买入'),
|
||
Tab(text: '卖出'),
|
||
]),
|
||
Expanded(child: TabBarView(children: [
|
||
OrderList(orders: orders),
|
||
OrderList(orders: orders.where((o) => o.side == OrderSide.buy)),
|
||
OrderList(orders: orders.where((o) => o.side == OrderSide.sell)),
|
||
])),
|
||
]),
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 15.4 换手机号
|
||
|
||
```dart
|
||
// lib/features/profile/domain/usecases/change_phone.dart
|
||
class ChangePhoneUseCase {
|
||
/// 换手机号:KYC身份验证(人脸+证件)后迁移账户
|
||
Future<Either<Failure, void>> call(ChangePhoneParams params) async {
|
||
// 1. 验证新手机号未注册
|
||
// 2. KYC身份验证(人脸识别 + 证件对比)
|
||
// 3. 旧手机号验证码确认
|
||
// 4. 更新映射表(旧手机→新手机,链上地址不变)
|
||
return _repo.changePhone(params);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 16. 离线核销增强
|
||
|
||
```dart
|
||
// lib/features/merchant/data/services/offline_redeem_service.dart
|
||
// 补充:离线核销限额与冲突处理
|
||
|
||
class OfflineRedeemConfig {
|
||
static const maxSingleAmount = 500.0; // 单笔离线核销限额$500
|
||
static const maxDailyAmount = 5000.0; // 单日离线核销限额$5,000
|
||
static const maxDailyCount = 50; // 单日离线核销笔数限制
|
||
}
|
||
|
||
class OfflineConflictResolver {
|
||
/// 冲突处理:同一张券被两个门店离线核销
|
||
/// 以先上链者为准,后者自动退回并通知
|
||
Future<void> resolveConflict(PendingRedemption local, ChainRedemption chain) async {
|
||
if (chain.redeemedBy != local.storeId) {
|
||
// 该券已被其他门店核销,本地核销无效
|
||
await _notifyStore(local.storeId, '券${local.couponCode}已被其他门店核销');
|
||
await _queue.delete(local.key);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 17. 争议与投诉
|
||
|
||
```dart
|
||
// lib/features/support/domain/entities/ticket.dart
|
||
@freezed
|
||
class SupportTicket with _$SupportTicket {
|
||
const factory SupportTicket({
|
||
required String id,
|
||
required TicketCategory category, // transaction/account/coupon
|
||
required String description,
|
||
required TicketStatus status,
|
||
String? orderId, // 关联订单号
|
||
List<String>? attachments,
|
||
}) = _SupportTicket;
|
||
}
|
||
|
||
enum TicketCategory { transaction, account, coupon, compliance, other }
|
||
enum TicketStatus { open, inProgress, waitingUser, resolved, closed }
|
||
```
|
||
|
||
---
|
||
|
||
## 18. 消息通知
|
||
|
||
```dart
|
||
// lib/features/notification/domain/entities/notification.dart
|
||
@freezed
|
||
class AppNotification with _$AppNotification {
|
||
const factory AppNotification({
|
||
required String id,
|
||
required NotificationType type,
|
||
required String title,
|
||
required String body,
|
||
required DateTime createdAt,
|
||
required bool isRead,
|
||
String? deepLink, // 点击跳转(如券详情、订单详情)
|
||
}) = _AppNotification;
|
||
}
|
||
|
||
enum NotificationType {
|
||
tradeComplete, // 交易完成
|
||
couponExpiringSoon, // 券即将过期
|
||
priceAlert, // 价格变动
|
||
transferReceived, // 收到转赠
|
||
systemAnnouncement, // 系统公告
|
||
issuerNotice, // 发行方公告
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
*文档版本: v2.0*
|
||
*基于: Genex 券交易平台 - 软件需求规格说明书 v4.1*
|
||
*技术栈: Flutter 3.x + Riverpod + Clean Architecture*
|
||
*更新: 补充Pro模式/助记词/社交恢复/AA钱包/外部钱包提取/转赠记录/余额/订单/换手机号/离线核销增强/争议投诉/消息通知*
|