feat(mining-app): 接入 capabilitiesProvider 实现 UI 层能力适配

Phase 5 补充:在 mining-app Flutter 前端关键页面接入 capability 检查,
被禁用的功能按钮置灰并显示 SnackBar 提示,后端 403 拦截作为兜底。

修改的页面及对应能力:
- send_shares_page: P2P_SEND → "确认发送"按钮
- receive_shares_page: P2P_RECEIVE → 顶部限制提示横幅
- c2c_market_page: C2C → "发布"按钮 + "接单"弹窗入口
- c2c_publish_page: C2C → "发布买入/卖出"按钮
- trading_page: TRADING → "确认交易"按钮
- edit_profile_page: PROFILE_EDIT → "保存"操作
- team_page: VIEW_TEAM → 页面数据加载前检查

设计原则:
- 不阻断页面浏览,只阻断操作
- fail-open: 能力获取失败时默认全部开启
- 禁用时点击按钮弹出 SnackBar 提示"您的XX功能已被限制"
- 沿用现有 disabled 按钮样式(.withOpacity(0.4))

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-28 01:36:24 -08:00
parent 21fc55fb01
commit fe9a30df85
7 changed files with 97 additions and 8 deletions

View File

@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../core/router/routes.dart';
import '../../providers/user_providers.dart';
import '../../../data/models/capability_model.dart';
class ReceiveSharesPage extends ConsumerWidget {
const ReceiveSharesPage({super.key});
@ -20,6 +21,8 @@ class ReceiveSharesPage extends ConsumerWidget {
final user = ref.watch(userNotifierProvider);
final phone = user.phone ?? '';
final nickname = user.nickname ?? user.realName ?? '股行用户';
final capabilities = ref.watch(capabilitiesProvider).valueOrNull ?? CapabilityMap.defaultAll();
final p2pReceiveEnabled = capabilities.p2pReceiveEnabled;
return Scaffold(
backgroundColor: _bgGray,
@ -57,6 +60,27 @@ class ReceiveSharesPage extends ConsumerWidget {
body: SingleChildScrollView(
child: Column(
children: [
if (!p2pReceiveEnabled)
Container(
margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFEF4444).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Row(
children: [
Icon(Icons.block, color: Color(0xFFEF4444), size: 20),
SizedBox(width: 8),
Expanded(
child: Text(
'您的P2P收款功能已被限制当前无法接收转账',
style: TextStyle(fontSize: 13, color: Color(0xFFEF4444)),
),
),
],
),
),
const SizedBox(height: 32),
//

View File

@ -8,6 +8,7 @@ import '../../providers/user_providers.dart';
import '../../providers/asset_providers.dart';
import '../../providers/transfer_providers.dart';
import '../../widgets/qr_scanner_sheet.dart';
import '../../../data/models/capability_model.dart';
class SendSharesPage extends ConsumerStatefulWidget {
const SendSharesPage({super.key});
@ -482,7 +483,10 @@ class _SendSharesPageState extends ConsumerState<SendSharesPage> {
final amount = double.tryParse(_amountController.text) ?? 0;
final available = double.tryParse(availableCash) ?? 0;
final totalRequired = amount + feeAmount;
final isValid = _isRecipientVerified &&
final capabilities = ref.watch(capabilitiesProvider).valueOrNull ?? CapabilityMap.defaultAll();
final p2pSendEnabled = capabilities.p2pSendEnabled;
final isValid = p2pSendEnabled &&
_isRecipientVerified &&
amount >= minTransferAmount &&
totalRequired <= available;
@ -494,7 +498,11 @@ class _SendSharesPageState extends ConsumerState<SendSharesPage> {
child: ElevatedButton(
onPressed: isValid && !transferState.isLoading
? () => _handleTransfer(feeAmount)
: null,
: !p2pSendEnabled
? () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('您的P2P转出功能已被限制'), backgroundColor: _red),
)
: null,
style: ElevatedButton.styleFrom(
backgroundColor: _orange,
foregroundColor: Colors.white,

View File

@ -8,6 +8,7 @@ import '../../../data/models/c2c_order_model.dart';
import '../../providers/c2c_providers.dart';
import '../../providers/user_providers.dart';
import '../../providers/asset_providers.dart';
import '../../../data/models/capability_model.dart';
class C2cMarketPage extends ConsumerStatefulWidget {
const C2cMarketPage({super.key});
@ -73,7 +74,16 @@ class _C2cMarketPageState extends ConsumerState<C2cMarketPage>
actions: [
IconButton(
icon: const Icon(Icons.add_circle_outline, color: _orange),
onPressed: () => context.push(Routes.c2cPublish),
onPressed: () {
final caps = ref.read(capabilitiesProvider).valueOrNull ?? CapabilityMap.defaultAll();
if (!caps.c2cEnabled) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('您的C2C交易功能已被限制'), backgroundColor: _red),
);
return;
}
context.push(Routes.c2cPublish);
},
),
],
bottom: TabBar(
@ -614,6 +624,13 @@ class _C2cMarketPageState extends ConsumerState<C2cMarketPage>
// #13 + #15: + BUY单收款信息输入
void _showTakeOrderDialog(C2cOrderModel order, bool isBuyAction) {
final caps = ref.read(capabilitiesProvider).valueOrNull ?? CapabilityMap.defaultAll();
if (!caps.c2cEnabled) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('您的C2C交易功能已被限制'), backgroundColor: _red),
);
return;
}
final quantityController = TextEditingController(text: order.quantity);
final paymentAccountController = TextEditingController();
final paymentRealNameController = TextEditingController();

View File

@ -7,6 +7,7 @@ import '../../providers/c2c_providers.dart';
import '../../providers/user_providers.dart';
import '../../providers/asset_providers.dart';
import '../../providers/trading_providers.dart';
import '../../../data/models/capability_model.dart';
class C2cPublishPage extends ConsumerStatefulWidget {
const C2cPublishPage({super.key});
@ -708,9 +709,11 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
Widget _buildPublishButton(C2cTradingState c2cState) {
final quantity = double.tryParse(_quantityController.text) ?? 0;
final isSell = _selectedType == 1;
final capabilities = ref.watch(capabilitiesProvider).valueOrNull ?? CapabilityMap.defaultAll();
final c2cEnabled = capabilities.c2cEnabled;
// #11: 11
bool isValid = quantity >= 1;
bool isValid = c2cEnabled && quantity >= 1;
if (isSell) {
//
if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) {
@ -726,7 +729,13 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: isValid && !c2cState.isLoading ? _handlePublish : null,
onPressed: isValid && !c2cState.isLoading
? _handlePublish
: !c2cEnabled
? () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('您的C2C交易功能已被限制'), backgroundColor: _red),
)
: null,
style: ElevatedButton.styleFrom(
backgroundColor: _selectedType == 0 ? _green : _red,
foregroundColor: Colors.white,

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../providers/user_providers.dart';
import '../../../data/models/capability_model.dart';
class EditProfilePage extends ConsumerStatefulWidget {
const EditProfilePage({super.key});
@ -336,6 +337,17 @@ class _EditProfilePageState extends ConsumerState<EditProfilePage> {
}
Future<void> _saveProfile() async {
final capabilities = ref.read(capabilitiesProvider).valueOrNull ?? CapabilityMap.defaultAll();
if (!capabilities.profileEditEnabled) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('您的编辑资料功能已被限制'),
backgroundColor: Color(0xFFEF4444),
),
);
return;
}
setState(() {
_isLoading = true;
});

View File

@ -5,6 +5,7 @@ import '../../../data/datasources/remote/referral_remote_datasource.dart';
import '../../widgets/team_tree_widget.dart';
import '../../providers/user_providers.dart';
import '../../providers/profile_providers.dart';
import '../../../data/models/capability_model.dart';
class TeamPage extends ConsumerStatefulWidget {
const TeamPage({super.key});
@ -31,6 +32,15 @@ class _TeamPageState extends ConsumerState<TeamPage> {
}
Future<void> _loadRootNode() async {
final capabilities = ref.read(capabilitiesProvider).valueOrNull ?? CapabilityMap.defaultAll();
if (!capabilities.viewTeamEnabled) {
setState(() {
_isLoading = false;
_error = '您的查看团队功能已被限制';
});
return;
}
final user = ref.read(userNotifierProvider);
final stats = ref.read(userStatsProvider).valueOrNull;

View File

@ -17,6 +17,7 @@ import '../../providers/asset_providers.dart';
import '../../widgets/shimmer_loading.dart';
import '../../widgets/kline_chart/kline_chart_widget.dart';
import '../../widgets/trade_password_dialog.dart';
import '../../../data/models/capability_model.dart';
class TradingPage extends ConsumerStatefulWidget {
const TradingPage({super.key});
@ -805,15 +806,23 @@ class _TradingPageState extends ConsumerState<TradingPage> {
),
),
const SizedBox(height: 24),
//
// +
Builder(builder: (context) {
final sellError = _selectedTab == 1 ? _getSellValidationError(tradingShareBalance) : null;
final isDisabled = sellError != null;
final capabilities = ref.watch(capabilitiesProvider).valueOrNull ?? CapabilityMap.defaultAll();
final tradingEnabled = capabilities.tradingEnabled;
final isDisabled = sellError != null || !tradingEnabled;
return SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: isDisabled ? null : _handleTrade,
onPressed: isDisabled
? !tradingEnabled
? () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('您的交易功能已被限制'), backgroundColor: AppColors.down),
)
: null
: _handleTrade,
style: ElevatedButton.styleFrom(
backgroundColor: isDisabled ? Colors.grey : _orange,
shape: RoundedRectangleBorder(