rwadurian/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart

1052 lines
35 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../../../core/constants/app_colors.dart';
import '../../../core/utils/format_utils.dart';
import '../../../data/models/trade_order_model.dart';
import '../../../domain/entities/price_info.dart';
import '../../../domain/entities/market_overview.dart';
import '../../../domain/entities/trade_order.dart';
import '../../../domain/entities/kline.dart';
import '../../providers/user_providers.dart';
import '../../providers/trading_providers.dart';
import '../../providers/asset_providers.dart';
import '../../widgets/shimmer_loading.dart';
import '../../widgets/kline_chart/kline_chart_widget.dart';
class TradingPage extends ConsumerStatefulWidget {
const TradingPage({super.key});
@override
ConsumerState<TradingPage> createState() => _TradingPageState();
}
class _TradingPageState extends ConsumerState<TradingPage> {
// 设计色彩
static const Color _orange = Color(0xFFFF6B00);
static const Color _green = Color(0xFF10B981);
static const Color _red = Color(0xFFEF4444);
static const Color _grayText = Color(0xFF6B7280);
static const Color _darkText = Color(0xFF1F2937);
static const Color _bgGray = Color(0xFFF3F4F6);
static const Color _lightGray = Color(0xFFF9FAFB);
static const Color _borderGray = Color(0xFFE5E7EB);
// 状态
int _selectedTab = 1; // 0: 买入, 1: 卖出
int _selectedTimeRange = 4; // 时间周期选择默认1时
final _quantityController = TextEditingController();
final _priceController = TextEditingController();
bool _isFullScreen = false; // K线图全屏状态
final List<String> _timeRanges = ['1分', '5分', '15分', '30分', '1时', '4时', ''];
@override
void dispose() {
_quantityController.dispose();
_priceController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final priceAsync = ref.watch(currentPriceProvider);
final marketAsync = ref.watch(marketOverviewProvider);
final ordersAsync = ref.watch(ordersProvider);
final klinesAsync = ref.watch(klinesProvider);
final user = ref.watch(userNotifierProvider);
final accountSequence = user.accountSequence ?? '';
// 全屏K线图模式
if (_isFullScreen) {
return KlineChartWidget(
klines: klinesAsync.valueOrNull ?? [],
currentPrice: priceAsync.valueOrNull?.price ?? '0',
isFullScreen: true,
onFullScreenToggle: () => setState(() => _isFullScreen = false),
timeRanges: _timeRanges,
selectedTimeIndex: _selectedTimeRange,
onTimeRangeChanged: (index) {
setState(() => _selectedTimeRange = index);
ref.read(selectedKlinePeriodProvider.notifier).state = _timeRanges[index];
},
);
}
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: SafeArea(
bottom: false,
child: RefreshIndicator(
onRefresh: () async {
ref.invalidate(currentPriceProvider);
ref.invalidate(marketOverviewProvider);
ref.invalidate(ordersProvider);
ref.invalidate(klinesProvider);
},
child: Column(
children: [
_buildAppBar(),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
_buildPriceCard(priceAsync),
_buildChartSection(priceAsync, klinesAsync),
_buildMarketDataCard(marketAsync),
_buildTradingPanel(priceAsync),
_buildMyOrdersCard(ordersAsync),
const SizedBox(height: 24),
],
),
),
),
],
),
),
),
);
}
Widget _buildAppBar() {
return Container(
color: _bgGray.withValues(alpha: 0.9),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: const Center(
child: Text(
'积分股交易',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF111827),
),
),
),
);
}
Widget _buildPriceCard(AsyncValue<PriceInfo?> priceAsync) {
final isLoading = priceAsync.isLoading;
final priceInfo = priceAsync.valueOrNull;
final hasError = priceAsync.hasError;
if (hasError && priceInfo == null) {
return _buildErrorCard('价格加载失败');
}
final price = priceInfo?.price ?? '0';
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'当前积分股价格',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _grayText,
),
),
const SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AmountText(
amount: priceInfo != null ? formatPrice(price) : null,
isLoading: isLoading,
prefix: '\u00A5 ',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: _orange,
letterSpacing: -0.75,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: _green.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.trending_up, size: 16, color: _green),
DataText(
data: isLoading ? null : '+0.00%',
isLoading: isLoading,
placeholder: '+--.--%',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _green,
),
),
],
),
),
],
),
],
),
);
}
Widget _buildChartSection(AsyncValue<PriceInfo?> priceAsync, AsyncValue<List<Kline>> klinesAsync) {
final priceInfo = priceAsync.valueOrNull;
final currentPrice = priceInfo?.price ?? '0.000000';
final klines = klinesAsync.valueOrNull ?? [];
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: klinesAsync.isLoading
? const SizedBox(
height: 280,
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
)
: KlineChartWidget(
klines: klines,
currentPrice: currentPrice,
isFullScreen: false,
onFullScreenToggle: () => setState(() => _isFullScreen = true),
timeRanges: _timeRanges,
selectedTimeIndex: _selectedTimeRange,
onTimeRangeChanged: (index) {
setState(() => _selectedTimeRange = index);
ref.read(selectedKlinePeriodProvider.notifier).state = _timeRanges[index];
},
),
);
}
Widget _buildMarketDataCard(AsyncValue<MarketOverview?> marketAsync) {
final isLoading = marketAsync.isLoading;
final market = marketAsync.valueOrNull;
final hasError = marketAsync.hasError;
if (hasError && market == null) {
return _buildErrorCard('市场数据加载失败');
}
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 4,
height: 16,
decoration: BoxDecoration(
color: _orange,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
const Text(
'市场数据',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
],
),
const SizedBox(height: 24),
Row(
children: [
_buildMarketDataItem(
'总积分股',
market != null ? formatCompact(market.totalShares) : null,
_orange,
isLoading,
),
Container(width: 1, height: 24, color: _bgGray),
const SizedBox(width: 16),
_buildMarketDataItem(
'流通池',
market != null ? formatCompact(market.circulationPool) : null,
_orange,
isLoading,
),
],
),
const SizedBox(height: 24),
Container(height: 1, color: _bgGray),
const SizedBox(height: 24),
Row(
children: [
_buildMarketDataItem(
'积分值池',
market != null ? formatCompact(market.greenPoints) : null,
_orange,
isLoading,
),
Container(width: 1, height: 24, color: _bgGray),
const SizedBox(width: 16),
_buildMarketDataItem(
'黑洞销毁量',
market != null ? formatCompact(market.blackHoleAmount) : null,
_red,
isLoading,
),
],
),
],
),
);
}
Widget _buildMarketDataItem(String label, String? value, Color valueColor, bool isLoading) {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: const TextStyle(fontSize: 12, color: _grayText)),
const SizedBox(height: 4),
DataText(
data: value,
isLoading: isLoading,
placeholder: '--,---,---',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: valueColor,
),
),
],
),
);
}
Widget _buildTradingPanel(AsyncValue<PriceInfo?> priceAsync) {
final priceInfo = priceAsync.valueOrNull;
final currentPrice = priceInfo?.price ?? '0';
// 获取用户资产信息
final user = ref.watch(userNotifierProvider);
final accountSequence = user.accountSequence ?? '';
final assetAsync = ref.watch(accountAssetProvider(accountSequence));
final asset = assetAsync.valueOrNull;
// 获取买入功能开关状态
final buyEnabledAsync = ref.watch(buyEnabledProvider);
final buyEnabled = buyEnabledAsync.valueOrNull ?? false;
// 可用积分股(交易账户)
final availableShares = asset?.availableShares ?? '0';
// 可用积分值(现金)
final availableCash = asset?.availableCash ?? '0';
// 设置默认价格
if (_priceController.text.isEmpty && priceInfo != null) {
_priceController.text = currentPrice;
}
// 如果选中买入但买入功能未开启,强制切换到卖出
if (_selectedTab == 0 && !buyEnabled) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _selectedTab == 0) {
setState(() => _selectedTab = 1);
}
});
}
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: _bgGray)),
),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: buyEnabled ? () => setState(() => _selectedTab = 0) : null,
child: Container(
padding: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: _selectedTab == 0 && buyEnabled ? _orange : Colors.transparent,
width: 2,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'买入',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: buyEnabled
? (_selectedTab == 0 ? _orange : _grayText)
: _grayText.withValues(alpha: 0.5),
),
),
if (!buyEnabled) ...[
const SizedBox(width: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: _grayText.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'待开启',
style: TextStyle(
fontSize: 10,
color: _grayText.withValues(alpha: 0.7),
),
),
),
],
],
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () => setState(() => _selectedTab = 1),
child: Container(
padding: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: _selectedTab == 1 ? _orange : Colors.transparent,
width: 2,
),
),
),
child: Text(
'卖出',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _selectedTab == 1 ? _orange : _grayText,
),
),
),
),
),
],
),
),
const SizedBox(height: 24),
// 买入功能未开启时显示提示
if (_selectedTab == 0 && !buyEnabled) ...[
Container(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Icon(
Icons.lock_outline,
size: 48,
color: _grayText.withValues(alpha: 0.5),
),
const SizedBox(height: 16),
const Text(
'买入功能待开启',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _grayText,
),
),
const SizedBox(height: 8),
Text(
'买入功能暂未开放,请耐心等待',
style: TextStyle(
fontSize: 14,
color: _grayText.withValues(alpha: 0.7),
),
),
],
),
),
] else ...[
// 可用余额提示
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: _orange.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_selectedTab == 0 ? '可用积分值' : '可用积分股',
style: const TextStyle(fontSize: 12, color: _grayText),
),
Text(
_selectedTab == 0
? formatAmount(availableCash)
: formatAmount(availableShares),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _orange,
),
),
],
),
),
const SizedBox(height: 16),
// 价格输入
_buildInputField('价格', _priceController, '请输入价格', '积分值'),
const SizedBox(height: 16),
// 数量输入 - 带"全部"按钮
_buildQuantityInputField(
'数量',
_quantityController,
'请输入数量',
'积分股',
_selectedTab == 1 ? availableShares : null,
_selectedTab == 0 ? availableCash : null,
currentPrice,
),
const SizedBox(height: 16),
// 预计获得/支出
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _bgGray,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_selectedTab == 0 ? '预计支出' : '预计获得',
style: const TextStyle(fontSize: 12, color: _grayText),
),
Text(
_calculateEstimate(),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _orange,
),
),
],
),
),
const SizedBox(height: 24),
// 提交按钮
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: _handleTrade,
style: ElevatedButton.styleFrom(
backgroundColor: _orange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
_selectedTab == 0 ? '买入积分股' : '卖出积分股',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
],
],
),
);
}
Widget _buildQuantityInputField(
String label,
TextEditingController controller,
String hint,
String suffix,
String? availableSharesForSell,
String? availableCashForBuy,
String currentPrice,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _grayText,
),
),
const SizedBox(height: 8),
Container(
height: 44,
decoration: BoxDecoration(
color: _bgGray,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: TextField(
controller: controller,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
hintText: hint,
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xFF9CA3AF),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
),
onChanged: (_) => setState(() {}),
),
),
// 全部按钮
GestureDetector(
onTap: () {
if (availableSharesForSell != null) {
// 卖出时填入全部可用积分股
controller.text = availableSharesForSell;
} else if (availableCashForBuy != null) {
// 买入时根据可用积分值计算可买数量
final price = double.tryParse(currentPrice) ?? 0;
final cash = double.tryParse(availableCashForBuy) ?? 0;
if (price > 0) {
final maxQuantity = cash / price;
controller.text = maxQuantity.toStringAsFixed(4);
}
}
setState(() {});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
margin: const EdgeInsets.only(right: 4),
decoration: BoxDecoration(
color: _orange.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: const Text(
'全部',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _orange,
),
),
),
),
Text(suffix, style: const TextStyle(fontSize: 12, color: _grayText)),
const SizedBox(width: 12),
],
),
),
],
);
}
Widget _buildInputField(
String label,
TextEditingController controller,
String hint,
String suffix,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _grayText,
),
),
const SizedBox(height: 8),
Container(
height: 44,
decoration: BoxDecoration(
color: _bgGray,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: TextField(
controller: controller,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
hintText: hint,
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xFF9CA3AF),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
),
),
),
Text(suffix, style: const TextStyle(fontSize: 12, color: _grayText)),
const SizedBox(width: 12),
],
),
),
],
);
}
String _calculateEstimate() {
final price = double.tryParse(_priceController.text) ?? 0;
final quantity = double.tryParse(_quantityController.text) ?? 0;
final total = price * quantity;
if (total == 0) {
return '0.00 积分值';
}
if (_selectedTab == 1) {
// 卖出时扣除10%销毁
final afterBurn = total * 0.9;
return '${formatAmount(afterBurn.toString())} 积分值';
}
return '${formatAmount(total.toString())} 积分值';
}
Widget _buildMyOrdersCard(AsyncValue<OrdersPageModel?> ordersAsync) {
final isLoading = ordersAsync.isLoading;
final ordersPage = ordersAsync.valueOrNull;
final orders = ordersPage?.data ?? [];
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'我的挂单',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
GestureDetector(
onTap: () {
// TODO: 查看全部挂单
},
child: const Text(
'全部 >',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _orange,
),
),
),
],
),
const SizedBox(height: 16),
if (isLoading)
const Center(
child: Padding(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(color: _orange),
),
)
else if (orders.isEmpty)
const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Text(
'暂无挂单',
style: TextStyle(fontSize: 14, color: _grayText),
),
)
else
Column(
children: orders.take(3).map((order) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: _buildOrderItemFromEntity(order),
)).toList(),
),
],
),
);
}
Widget _buildOrderItemFromEntity(TradeOrder order) {
final isSell = order.isSell;
final dateFormat = DateFormat('MM/dd HH:mm');
final formattedDate = dateFormat.format(order.createdAt);
String statusText;
switch (order.status) {
case OrderStatus.pending:
statusText = '待成交';
break;
case OrderStatus.partial:
statusText = '部分成交';
break;
case OrderStatus.filled:
statusText = '已成交';
break;
case OrderStatus.cancelled:
statusText = '已取消';
break;
}
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _lightGray,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: _bgGray),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: (isSell ? _red : _green).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
isSell ? '卖出' : '买入',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: isSell ? _red : _green,
),
),
),
const SizedBox(width: 8),
Text(
formatPrice(order.price),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
],
),
const SizedBox(height: 4),
Text(
formattedDate,
style: const TextStyle(fontSize: 12, color: _grayText),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${formatCompact(order.quantity)}',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: _darkText,
),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: _orange.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(9999),
),
child: Text(
statusText,
style: const TextStyle(fontSize: 12, color: _orange),
),
),
],
),
],
),
);
}
Widget _buildErrorCard(String message) {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: Column(
children: [
const Icon(Icons.error_outline, size: 48, color: AppColors.error),
const SizedBox(height: 8),
Text(message),
],
),
),
);
}
void _handleTrade() async {
if (_priceController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请输入价格')),
);
return;
}
if (_quantityController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请输入数量')),
);
return;
}
final isBuy = _selectedTab == 0;
final price = double.tryParse(_priceController.text) ?? 0;
final quantity = double.tryParse(_quantityController.text) ?? 0;
// 卖出时显示确认弹窗
if (!isBuy) {
final total = price * quantity;
final burned = total * 0.1;
final received = total * 0.9;
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认卖出'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('卖出数量: ${formatAmount(quantity.toString())} 积分股'),
const SizedBox(height: 8),
Text('卖出价格: ${formatPrice(price.toString())} 积分值'),
const SizedBox(height: 8),
Text('交易总额: ${formatAmount(total.toString())} 积分值'),
const SizedBox(height: 8),
Text(
'进入积分股池: ${formatAmount(burned.toString())} 积分值 (10%)',
style: const TextStyle(color: _green),
),
const SizedBox(height: 8),
Text(
'实际获得: ${formatAmount(received.toString())} 积分值',
style: const TextStyle(
fontWeight: FontWeight.bold,
color: _green,
),
),
const SizedBox(height: 16),
const Text(
'注意: 卖出积分股将扣除10%进入积分股池,此操作不可撤销。',
style: TextStyle(
fontSize: 12,
color: _grayText,
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text(
'确认卖出',
style: TextStyle(color: _orange),
),
),
],
),
);
if (confirmed != true) return;
}
bool success;
if (isBuy) {
success = await ref
.read(tradingNotifierProvider.notifier)
.buyShares(_priceController.text, _quantityController.text);
} else {
success = await ref
.read(tradingNotifierProvider.notifier)
.sellShares(_priceController.text, _quantityController.text);
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? (isBuy ? '买入订单已提交' : '卖出订单已提交')
: (isBuy ? '买入失败' : '卖出失败')),
backgroundColor: success ? _green : AppColors.error,
),
);
if (success) {
_quantityController.clear();
// 交易成功后刷新订单列表和资产
ref.invalidate(ordersProvider);
ref.invalidate(currentPriceProvider);
ref.invalidate(marketOverviewProvider);
// 刷新资产数据
final user = ref.read(userNotifierProvider);
ref.invalidate(accountAssetProvider(user.accountSequence ?? ''));
}
}
}
}