docs(genex-mobile): 完善邀请分享模块注释与说明

SharePage:
  - 文件头注释:完整功能概述、支持的分享场景、数据来源、URL格式、依赖包说明
  - 类注释:生命周期描述、Widget 树结构图(ASCII)
  - 状态变量:详细说明 _info/_loading/_error/_baseInviteUrl
  - _inviteLink:注释已加载/未加载两种输出示例
  - _loadInfo:成功/失败两条路径说明
  - _showCopied:SnackBar 样式描述
  - _copyCode/_copyLink:示例复制内容
  - _shareNative:iOS/Android 行为说明 + 文案模板示例
  - _buildHeroCard:视觉层次注释 + QR 参数说明
  - _buildStatsCard:布局描述 + 数据来源注释
  - _buildShareActions:三项操作的点击行为说明

ReferralService:
  - 文件头:完整端点一览、推荐码格式、推荐链规则
  - ReferralInfo:字段含义 + 后端响应 JSON 示例
  - getMyInfo:登录要求说明
  - validateCode:用途说明 + 返回值降级策略
  - getDirectReferrals:分页参数范围 + 响应 JSON 示例

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-04 01:42:47 -08:00
parent 46d2404d19
commit 1c36c849e2
2 changed files with 261 additions and 32 deletions

View File

@ -1,14 +1,54 @@
import '../network/api_client.dart'; import '../network/api_client.dart';
/// // ============================================================
// ReferralService /
//
// referral-service 3013 Kong :8080
//
//
// GET /api/v1/referral/my JWT
// GET /api/v1/referral/validate
// GET /api/v1/referral/direct JWT
//
// GNX + 5 0/O/I/1GNXAB2C3
// 50
// ============================================================
///
///
/// [GET /api/v1/referral/my] `data`
///
///
/// ```json
/// {
/// "code": 0,
/// "data": {
/// "userId": "uuid-...",
/// "referralCode": "GNXAB2C3",
/// "referrerId": "uuid-... | null",
/// "usedCode": "GNXPARENT | null",
/// "directReferralCount": 5,
/// "totalTeamCount": 23
/// }
/// }
/// ```
class ReferralInfo { class ReferralInfo {
/// GNXxxxxx 8
final String referralCode; final String referralCode;
///
final int directReferralCount; final int directReferralCount;
///
final int totalTeamCount; final int totalTeamCount;
/// userId null
final String? referrerId; final String? referrerId;
/// referrerId referralCode便
final String? usedCode; final String? usedCode;
ReferralInfo({ const ReferralInfo({
required this.referralCode, required this.referralCode,
required this.directReferralCount, required this.directReferralCount,
required this.totalTeamCount, required this.totalTeamCount,
@ -25,9 +65,22 @@ class ReferralInfo {
usedCode: json['usedCode'] as String?, usedCode: json['usedCode'] as String?,
); );
} }
@override
String toString() =>
'ReferralInfo(code=$referralCode, direct=$directReferralCount, team=$totalTeamCount)';
} }
/// Referral Service referral-service API ///
///
///
/// ```dart
/// final info = await ReferralService.instance.getMyInfo();
/// print(info.referralCode); // e.g. GNXAB2C3
/// ```
///
/// ApiClient JWT
/// token DioException401
class ReferralService { class ReferralService {
static final ReferralService _instance = ReferralService._(); static final ReferralService _instance = ReferralService._();
static ReferralService get instance => _instance; static ReferralService get instance => _instance;
@ -35,14 +88,56 @@ class ReferralService {
final _api = ApiClient.instance; final _api = ApiClient.instance;
/// //
///
///
///
///
/// - Bearer Token
/// - Kafka
///
/// Throws [DioException] on network error or 401/403.
Future<ReferralInfo> getMyInfo() async { Future<ReferralInfo> getMyInfo() async {
final resp = await _api.get('/api/v1/referral/my'); final resp = await _api.get('/api/v1/referral/my');
final data = resp.data['data'] as Map<String, dynamic>; final data = resp.data['data'] as Map<String, dynamic>;
return ReferralInfo.fromJson(data); return ReferralInfo.fromJson(data);
} }
/// ///
///
///
///
/// `{ "code": 0, "data": { "valid": true/false } }`
///
/// Returns `false` on any error (avoid blocking registration flow).
Future<bool> validateCode(String code) async {
try {
final resp = await _api.get(
'/api/v1/referral/validate',
queryParameters: {'code': code.toUpperCase()},
);
final data = resp.data['data'] as Map<String, dynamic>;
return data['valid'] == true;
} catch (_) {
return false;
}
}
///
///
/// userId
///
/// - [offset] 0
/// - [limit] 50
///
/// `data`
/// ```json
/// {
/// "items": [ { "userId": "...", "referralCode": "...", "createdAt": "..." } ],
/// "total": 5
/// }
/// ```
Future<List<Map<String, dynamic>>> getDirectReferrals({ Future<List<Map<String, dynamic>>> getDirectReferrals({
int offset = 0, int offset = 0,
int limit = 20, int limit = 20,

View File

@ -1,3 +1,35 @@
// ============================================================
// SharePage / 广
//
// /share ProfilePage
//
//
// 1. Hero Card
// · qr_flutter URL
// ·
// 2. +
// 3. / /
// 4.
//
// App
// / QQ / WhatsApp / Telegram / Line / Twitter/X
// / / AirDrop / /
//
//
// GET /api/v1/referral/my ReferralService.getMyInfo()
// Kafka
// Loading
//
//
// https://app.gogenex.com/download?ref={referralCode}
// App Store / Google Play /
// ref
//
//
// qr_flutter ^4.1.0
// share_plus ^10.0.2 iOS Share Sheet / Android Sharesheet
// ============================================================
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
@ -11,11 +43,18 @@ import '../../../../app/i18n/app_localizations.dart';
/// A9. / 广 /// A9. / 广
/// ///
/// ///
/// - App /// initState _loadInfo() [Loading] [Content | Error]
/// - / ///
/// - WhatsAppTelegram /// Widget
/// - + /// ```
/// Scaffold
/// SingleChildScrollView
/// _buildHeroCard() QR +
/// _buildStatsCard() |
/// _buildShareActions() / /
/// GenexButton
/// ```
class SharePage extends StatefulWidget { class SharePage extends StatefulWidget {
const SharePage({super.key}); const SharePage({super.key});
@ -24,23 +63,52 @@ class SharePage extends StatefulWidget {
} }
class _SharePageState extends State<SharePage> { class _SharePageState extends State<SharePage> {
//
/// referral-service null
ReferralInfo? _info; ReferralInfo? _info;
///
bool _loading = true; bool _loading = true;
///
String? _error; String? _error;
/// App //
/// App
///
///
/// 1. UAiOS / Android /
/// 2. iOS App Store
/// 3. Android Google Play APK
/// 4. `ref` localStorage
static const String _baseInviteUrl = 'https://app.gogenex.com/download'; static const String _baseInviteUrl = 'https://app.gogenex.com/download';
//
/// + 使
///
/// https://app.gogenex.com/download?ref=GNXAB2C3
/// https://app.gogenex.com/download
String get _inviteLink => _info != null String get _inviteLink => _info != null
? '$_baseInviteUrl?ref=${_info!.referralCode}' ? '$_baseInviteUrl?ref=${_info!.referralCode}'
: _baseInviteUrl; : _baseInviteUrl;
//
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadInfo(); _loadInfo();
} }
//
/// referral-service
///
/// [_info] loading
/// [_error]
Future<void> _loadInfo() async { Future<void> _loadInfo() async {
setState(() { setState(() {
_loading = true; _loading = true;
@ -54,6 +122,9 @@ class _SharePageState extends State<SharePage> {
} }
} }
//
/// SnackBar +
void _showCopied(String message) { void _showCopied(String message) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@ -74,17 +145,33 @@ class _SharePageState extends State<SharePage> {
); );
} }
///
///
/// GNXAB2C3
Future<void> _copyCode() async { Future<void> _copyCode() async {
if (_info == null) return; if (_info == null) return;
await Clipboard.setData(ClipboardData(text: _info!.referralCode)); await Clipboard.setData(ClipboardData(text: _info!.referralCode));
if (mounted) _showCopied(context.t('share.codeCopied')); if (mounted) _showCopied(context.t('share.codeCopied'));
} }
///
///
/// https://app.gogenex.com/download?ref=GNXAB2C3
Future<void> _copyLink() async { Future<void> _copyLink() async {
await Clipboard.setData(ClipboardData(text: _inviteLink)); await Clipboard.setData(ClipboardData(text: _inviteLink));
if (mounted) _showCopied(context.t('share.linkCopied')); if (mounted) _showCopied(context.t('share.linkCopied'));
} }
///
///
/// +
///
/// iOS Share SheetAirDrop
/// Android SharesheetWhatsAppTelegram
///
/// zh-CN
/// Genex 使 {code}
/// https://app.gogenex.com/download?ref={code}
Future<void> _shareNative() async { Future<void> _shareNative() async {
final code = _info?.referralCode ?? ''; final code = _info?.referralCode ?? '';
final text = context final text = context
@ -94,6 +181,8 @@ class _SharePageState extends State<SharePage> {
await Share.share(text, subject: context.t('share.nativeShareTitle')); await Share.share(text, subject: context.t('share.nativeShareTitle'));
} }
// Build
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -116,6 +205,8 @@ class _SharePageState extends State<SharePage> {
); );
} }
//
Widget _buildLoading() { Widget _buildLoading() {
return Center( return Center(
child: Column( child: Column(
@ -132,6 +223,9 @@ class _SharePageState extends State<SharePage> {
); );
} }
//
/// + +
Widget _buildError() { Widget _buildError() {
return Center( return Center(
child: Padding( child: Padding(
@ -159,19 +253,21 @@ class _SharePageState extends State<SharePage> {
); );
} }
//
Widget _buildContent() { Widget _buildContent() {
return SingleChildScrollView( return SingleChildScrollView(
padding: AppSpacing.pagePadding, padding: AppSpacing.pagePadding,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
_buildHeroCard(), _buildHeroCard(), // QR +
const SizedBox(height: 16), const SizedBox(height: 16),
_buildStatsCard(), _buildStatsCard(), //
const SizedBox(height: 16), const SizedBox(height: 16),
_buildShareActions(), _buildShareActions(), //
const SizedBox(height: 24), const SizedBox(height: 24),
GenexButton( GenexButton( //
label: context.t('share.shareToFriend'), label: context.t('share.shareToFriend'),
onPressed: _shareNative, onPressed: _shareNative,
), ),
@ -181,7 +277,18 @@ class _SharePageState extends State<SharePage> {
); );
} }
// Hero Card: QR + // Hero Card +
//
//
// Container[ + 20 + ]
// Column
// + "扫码下载 Genex"
// "Genex" 3FontWeight.w800
// Container[] QrImageView180×180
// +
//
// QR _inviteLink https://app.gogenex.com/download?ref=GNXAB2C3
// QR eye= #4834D4module= #6C5CE7=
Widget _buildHeroCard() { Widget _buildHeroCard() {
return Container( return Container(
@ -200,7 +307,7 @@ class _SharePageState extends State<SharePage> {
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28),
child: Column( child: Column(
children: [ children: [
// //
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -223,7 +330,8 @@ class _SharePageState extends State<SharePage> {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// QR //
// QrImageView
Container( Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -238,14 +346,16 @@ class _SharePageState extends State<SharePage> {
], ],
), ),
child: QrImageView( child: QrImageView(
data: _inviteLink, data: _inviteLink, // URL
version: QrVersions.auto, version: QrVersions.auto, //
size: 180, size: 180,
backgroundColor: Colors.white, backgroundColor: Colors.white,
//
eyeStyle: const QrEyeStyle( eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.square, eyeShape: QrEyeShape.square,
color: Color(0xFF4834D4), color: Color(0xFF4834D4),
), ),
//
dataModuleStyle: const QrDataModuleStyle( dataModuleStyle: const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square, dataModuleShape: QrDataModuleShape.square,
color: Color(0xFF6C5CE7), color: Color(0xFF6C5CE7),
@ -254,7 +364,8 @@ class _SharePageState extends State<SharePage> {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// //
//
Text( Text(
context.t('share.myReferralCode'), context.t('share.myReferralCode'),
style: AppTypography.caption.copyWith(color: Colors.white60), style: AppTypography.caption.copyWith(color: Colors.white60),
@ -265,13 +376,14 @@ class _SharePageState extends State<SharePage> {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15), color: Colors.white.withOpacity(0.15), //
borderRadius: BorderRadius.circular(40), borderRadius: BorderRadius.circular(40),
border: Border.all(color: Colors.white30), border: Border.all(color: Colors.white30),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// + 4
Text( Text(
_info?.referralCode ?? '------', _info?.referralCode ?? '------',
style: AppTypography.h2.copyWith( style: AppTypography.h2.copyWith(
@ -282,6 +394,7 @@ class _SharePageState extends State<SharePage> {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
//
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -313,7 +426,14 @@ class _SharePageState extends State<SharePage> {
); );
} }
// //
//
// 线
// Icons.people
// Icons.groups
//
// ReferralInfo.directReferralCount / totalTeamCount
// 0
Widget _buildStatsCard() { Widget _buildStatsCard() {
return Container( return Container(
@ -333,6 +453,7 @@ class _SharePageState extends State<SharePage> {
label: context.t('share.directReferrals'), label: context.t('share.directReferrals'),
), ),
), ),
// 线
Container(width: 1, height: 44, color: AppColors.borderLight), Container(width: 1, height: 44, color: AppColors.borderLight),
Expanded( Expanded(
child: _buildStatItem( child: _buildStatItem(
@ -346,6 +467,7 @@ class _SharePageState extends State<SharePage> {
); );
} }
/// + +
Widget _buildStatItem({ Widget _buildStatItem({
required IconData icon, required IconData icon,
required String value, required String value,
@ -358,22 +480,27 @@ class _SharePageState extends State<SharePage> {
children: [ children: [
Icon(icon, size: 16, color: AppColors.primary), Icon(icon, size: 16, color: AppColors.primary),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(value, style: AppTypography.h2.copyWith(color: AppColors.primary)),
value,
style: AppTypography.h2.copyWith(color: AppColors.primary),
),
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(label, style: AppTypography.caption.copyWith(color: AppColors.textSecondary)),
label,
style: AppTypography.caption.copyWith(color: AppColors.textSecondary),
),
], ],
); );
} }
// //
//
// ListTile Divider
//
// [] GNXAB2C3 >
// [] https://... >
// [绿] /WhatsApp >
//
//
// _copyCode() + SnackBar
// _copyLink() + SnackBar
// _shareNative()
Widget _buildShareActions() { Widget _buildShareActions() {
return Column( return Column(
@ -395,6 +522,7 @@ class _SharePageState extends State<SharePage> {
), ),
child: Column( child: Column(
children: [ children: [
//
_buildActionTile( _buildActionTile(
icon: Icons.qr_code_rounded, icon: Icons.qr_code_rounded,
iconBg: AppColors.primaryContainer, iconBg: AppColors.primaryContainer,
@ -404,6 +532,7 @@ class _SharePageState extends State<SharePage> {
onTap: _copyCode, onTap: _copyCode,
), ),
const Divider(indent: 60, height: 1), const Divider(indent: 60, height: 1),
//
_buildActionTile( _buildActionTile(
icon: Icons.link_rounded, icon: Icons.link_rounded,
iconBg: AppColors.infoLight, iconBg: AppColors.infoLight,
@ -413,6 +542,7 @@ class _SharePageState extends State<SharePage> {
onTap: _copyLink, onTap: _copyLink,
), ),
const Divider(indent: 60, height: 1), const Divider(indent: 60, height: 1),
//
_buildActionTile( _buildActionTile(
icon: Icons.share_rounded, icon: Icons.share_rounded,
iconBg: AppColors.successLight, iconBg: AppColors.successLight,
@ -428,6 +558,10 @@ class _SharePageState extends State<SharePage> {
); );
} }
///
///
/// - [iconBg] 使
/// - [subtitle] /
Widget _buildActionTile({ Widget _buildActionTile({
required IconData icon, required IconData icon,
required Color iconBg, required Color iconBg,