rwadurian/frontend/mining-app/lib/presentation/pages/asset/asset_page.dart

953 lines
32 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 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/router/routes.dart';
import '../../../core/utils/format_utils.dart';
import '../../../core/network/price_websocket_service.dart';
import '../../../core/constants/app_constants.dart';
import '../../../core/constants/app_colors.dart';
import '../../../domain/entities/asset_display.dart';
import '../../providers/user_providers.dart';
import '../../providers/asset_providers.dart';
import '../../providers/mining_providers.dart';
import '../../widgets/shimmer_loading.dart';
class AssetPage extends ConsumerStatefulWidget {
const AssetPage({super.key});
@override
ConsumerState<AssetPage> createState() => _AssetPageState();
}
class _AssetPageState extends ConsumerState<AssetPage> {
// 设计色彩
static const Color _orange = Color(0xFFFF6B00);
static const Color _green = Color(0xFF10B981);
static const Color _grayText = Color(0xFF6B7280);
static const Color _darkText = Color(0xFF1F2937);
static const Color _bgGray = Color(0xFFF3F4F6);
static const Color _riverBed = Color(0xFF4B5563);
static const Color _serenade = Color(0xFFFFF7ED);
static const Color _feta = Color(0xFFF0FDF4);
static const Color _scandal = Color(0xFFDCFCE7);
static const Color _jewel = Color(0xFF15803D);
// 实时刷新相关状态
Timer? _refreshTimer;
int _elapsedSeconds = 0;
double _initialShareBalance = 0;
double _growthPerSecond = 0;
String? _lastAccountSequence;
bool _timerStarted = false;
// WebSocket 相关
StreamSubscription<PriceUpdate>? _priceSubscription;
String _currentPrice = '0';
String _currentBurnMultiplier = '0';
@override
void initState() {
super.initState();
_connectWebSocket();
}
@override
void dispose() {
_disconnectWebSocket();
_refreshTimer?.cancel();
super.dispose();
}
/// 连接 WebSocket
void _connectWebSocket() {
final wsService = PriceWebSocketService.instance;
wsService.connect(AppConstants.baseUrl);
// 监听价格更新
_priceSubscription = wsService.priceUpdates.listen((update) {
if (mounted) {
setState(() {
_currentPrice = update.price;
_currentBurnMultiplier = update.burnMultiplier;
});
}
});
}
/// 断开 WebSocket
void _disconnectWebSocket() {
_priceSubscription?.cancel();
_priceSubscription = null;
PriceWebSocketService.instance.disconnect();
}
/// 启动定时器(使用外部传入的每秒增长值)
void _startTimerWithGrowth(AssetDisplay asset, String perSecondEarning) {
// 防止重复启动
if (_timerStarted && _refreshTimer != null) {
return;
}
_refreshTimer?.cancel();
_elapsedSeconds = 0;
_initialShareBalance = double.tryParse(asset.shareBalance) ?? 0;
// 使用传入的每秒增长值(来自 mining-service
_growthPerSecond = double.tryParse(perSecondEarning) ?? 0;
// 初始化价格(如果 WebSocket 还没推送)
if (_currentPrice == '0') {
_currentPrice = asset.currentPrice;
_currentBurnMultiplier = asset.burnMultiplier;
}
_timerStarted = true;
_refreshTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (mounted) {
setState(() {
_elapsedSeconds++;
});
}
});
}
/// 重置定时器(刷新时调用)
void _resetTimer() {
_refreshTimer?.cancel();
_refreshTimer = null;
_elapsedSeconds = 0;
_timerStarted = false;
}
/// 计算当前实时总资产显示值
/// 总资产 = 积分股价值 + 积分值余额
/// 积分股价值 = 当前积分股余额 × (1 + burnMultiplier) × price
/// 积分值余额 = 可用积分值 + 冻结积分值
double _calculateTotalAssetValue(AssetDisplay? asset) {
// 优先使用 WebSocket 推送的价格,否则使用 API 返回的价格
final price = double.tryParse(_currentPrice) ?? 0;
final burnMultiplier = double.tryParse(_currentBurnMultiplier) ?? 0;
final multiplierFactor = 1 + burnMultiplier;
// 积分股价值
final shareValue = _currentShareBalance * multiplierFactor * price;
// 积分值余额(现金 = 可用 + 冻结)
final availableCash = double.tryParse(asset?.availableCash ?? '0') ?? 0;
final frozenCash = double.tryParse(asset?.frozenCash ?? '0') ?? 0;
final totalCash = availableCash + frozenCash;
return shareValue + totalCash;
}
/// 计算当前实时积分股余额
double get _currentShareBalance {
return _initialShareBalance + (_elapsedSeconds * _growthPerSecond);
}
/// 计算当前有效积分股(含倍数)
double get _currentEffectiveShares {
final burnMultiplier = double.tryParse(_currentBurnMultiplier) ?? 0;
return _currentShareBalance * (1 + burnMultiplier);
}
AssetDisplay? _lastAsset;
@override
Widget build(BuildContext context) {
final user = ref.watch(userNotifierProvider);
final accountSequence = user.accountSequence ?? '';
// 使用 public API不依赖 JWT token
final assetAsync = ref.watch(accountAssetProvider(accountSequence));
// 从 mining-service 获取每秒收益
final shareAccountAsync = ref.watch(shareAccountProvider(accountSequence));
// 提取数据和加载状态
final isLoading = assetAsync.isLoading || accountSequence.isEmpty;
final asset = assetAsync.valueOrNull;
final shareAccount = shareAccountAsync.valueOrNull;
// 获取每秒收益(优先使用 mining-service 的数据)
final perSecondEarning = shareAccount?.perSecondEarning ?? '0';
final hasValidGrowth = (double.tryParse(perSecondEarning) ?? 0) > 0;
// 当数据加载完成时启动定时器
if (asset != null && hasValidGrowth) {
// 账户切换或首次加载时重置并启动定时器
if (_lastAccountSequence != accountSequence) {
_lastAccountSequence = accountSequence;
_lastAsset = asset;
_resetTimer();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _startTimerWithGrowth(asset, perSecondEarning);
});
} else if (!_timerStarted) {
// 定时器未启动时启动(例如页面刚进入)
_lastAsset = asset;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _startTimerWithGrowth(asset, perSecondEarning);
});
} else {
_lastAsset = asset;
}
} else if (asset != null) {
_lastAsset = asset;
}
return Scaffold(
backgroundColor: AppColors.backgroundOf(context),
body: SafeArea(
bottom: false,
child: LayoutBuilder(
builder: (context, constraints) {
return RefreshIndicator(
onRefresh: () async {
_resetTimer();
_lastAsset = null;
ref.invalidate(accountAssetProvider(accountSequence));
ref.invalidate(shareAccountProvider(accountSequence));
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: Column(
children: [
// 顶部导航栏
_buildAppBar(context, user),
// 内容
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
const SizedBox(height: 8),
// 总资产卡片 - 始终显示,数字部分闪烁,实时刷新
_buildTotalAssetCard(context, asset, isLoading, _calculateTotalAssetValue(asset), _currentShareBalance, perSecondEarning),
const SizedBox(height: 24),
// 快捷操作按钮
_buildQuickActions(context),
const SizedBox(height: 24),
// 资产列表 - 始终显示,数字部分闪烁,实时刷新
_buildAssetList(context, asset, isLoading, _currentShareBalance, perSecondEarning),
const SizedBox(height: 24),
// 交易统计
_buildEarningsCard(context, asset, isLoading),
const SizedBox(height: 24),
// 账户列表
_buildAccountList(context, asset, isLoading),
const SizedBox(height: 24),
],
),
),
],
),
),
),
);
},
),
),
);
}
Widget _buildAppBar(BuildContext context, user) {
return Container(
color: AppColors.surfaceOf(context).withOpacity(0.9),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Center(
child: Text(
'我的资产',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textPrimaryOf(context),
),
),
),
);
}
Widget _buildTotalAssetCard(BuildContext context, AssetDisplay? asset, bool isLoading, double totalAssetValue, double currentShareBalance, String perSecondEarning) {
// 使用传入的每秒增长值(来自 mining-service
final growthPerSecond = double.tryParse(perSecondEarning) ?? 0.0;
final isDark = AppColors.isDark(context);
// 使用实时计算的总资产值(积分股价值 + 积分值余额)
final displayValue = asset != null && totalAssetValue > 0
? totalAssetValue.toString()
: asset?.displayAssetValue;
// 计算有效积分股(含倍数)= 实时积分股余额 × (1 + burnMultiplier)
final burnMultiplier = double.tryParse(asset?.burnMultiplier ?? '0') ?? 0;
final effectiveShareBalance = asset != null && currentShareBalance > 0
? (currentShareBalance * (1 + burnMultiplier)).toString()
: asset?.effectiveShares;
return Container(
decoration: BoxDecoration(
color: AppColors.cardOf(context),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: isDark ? Colors.black.withOpacity(0.3) : Colors.black.withOpacity(0.04),
blurRadius: 30,
offset: const Offset(0, 8),
),
],
),
child: Stack(
children: [
// 背景装饰圆
Positioned(
right: -20,
top: -40,
child: Container(
width: 128,
height: 128,
decoration: BoxDecoration(
color: isDark ? _orange.withOpacity(0.1) : _serenade,
borderRadius: BorderRadius.circular(64),
),
),
),
// 顶部渐变条
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
height: 4,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFFF6B00), Color(0xFFFDBA74)],
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
),
),
// 内容
Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行
Row(
children: [
Text(
'总资产估值',
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondaryOf(context),
),
),
const SizedBox(width: 8),
Icon(
Icons.visibility_outlined,
size: 14,
color: AppColors.textMutedOf(context),
),
],
),
const SizedBox(height: 8),
// 金额 - 实时刷新显示
AmountText(
amount: displayValue != null ? formatAmount(displayValue) : null,
isLoading: isLoading,
prefix: '¥ ',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: _orange,
letterSpacing: -0.75,
),
),
const SizedBox(height: 4),
// 有效积分股(含倍数)- 暂时隐藏
// DataText(
// data: effectiveShareBalance != null ? '≈ ${formatCompact(effectiveShareBalance)} 积分股 (含倍数)' : null,
// isLoading: isLoading,
// placeholder: '≈ -- 积分股',
// style: const TextStyle(
// fontSize: 14,
// color: Color(0xFF9CA3AF),
// ),
// ),
const SizedBox(height: 12),
// 每秒增长
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isDark ? _green.withOpacity(0.15) : _feta,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.bolt, size: 14, color: _green),
const SizedBox(width: 6),
DataText(
data: asset != null
? '+${formatDecimal(growthPerSecond.toString(), 8)}/秒'
: null,
isLoading: isLoading,
placeholder: '+--/秒',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: _green,
),
),
],
),
),
],
),
),
],
),
);
}
Widget _buildQuickActions(BuildContext context) {
final isDark = AppColors.isDark(context);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildQuickActionItem(
context,
Icons.add,
'接收',
_orange,
() => context.push(Routes.receiveShares),
isDark,
),
_buildQuickActionItem(
context,
Icons.remove,
'发送',
_orange,
() => context.push(Routes.sendShares),
isDark,
),
_buildQuickActionItem(
context,
Icons.people_outline,
'C2C',
_orange,
() => context.push(Routes.c2cMarket),
isDark,
),
],
);
}
Widget _buildQuickActionItem(
BuildContext context,
IconData icon,
String label,
Color color,
VoidCallback onTap,
bool isDark,
) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Column(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: isDark ? _orange.withOpacity(0.15) : _serenade,
borderRadius: BorderRadius.circular(16),
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(height: 8),
Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: AppColors.textSecondaryOf(context),
),
),
],
),
);
}
Widget _buildAssetList(BuildContext context, AssetDisplay? asset, bool isLoading, double currentShareBalance, String perSecondEarning) {
// 使用实时积分股余额
final shareBalance = asset != null && currentShareBalance > 0
? currentShareBalance
: double.tryParse(asset?.shareBalance ?? '0') ?? 0;
final multiplier = double.tryParse(asset?.burnMultiplier ?? '0') ?? 0;
final multipliedAsset = shareBalance * multiplier;
final currentPrice = double.tryParse(asset?.currentPrice ?? '0') ?? 0;
final isDark = AppColors.isDark(context);
return Column(
children: [
// 积分股 - 实时刷新
_buildAssetItem(
context: context,
icon: Icons.trending_up,
iconColor: _orange,
iconBgColor: isDark ? _orange.withOpacity(0.15) : _serenade,
title: '积分股',
amount: asset != null ? shareBalance.toString() : null,
isLoading: isLoading,
valueInCny: asset != null
? '¥${formatAmount((shareBalance * currentPrice).toString())}'
: null,
// tag: asset != null ? '含倍数资产: ${formatCompact(multipliedAsset.toString())}' : null, // 暂时隐藏
growthText: asset != null ? '每秒 +${formatDecimal(perSecondEarning, 8)}' : null,
),
const SizedBox(height: 16),
// 积分值(现金余额)
_buildAssetItem(
context: context,
icon: Icons.eco,
iconColor: _green,
iconBgColor: isDark ? _green.withOpacity(0.15) : _feta,
title: '积分值',
amount: asset?.cashBalance,
isLoading: isLoading,
valueInCny: asset != null ? '¥${formatAmount(asset.cashBalance)}' : null,
),
const SizedBox(height: 16),
// 冻结积分股
_buildAssetItem(
context: context,
icon: Icons.lock_outline,
iconColor: _orange,
iconBgColor: isDark ? _orange.withOpacity(0.15) : _serenade,
title: '冻结积分股',
amount: asset?.frozenShares,
isLoading: isLoading,
subtitle: '交易挂单中',
),
],
);
}
Widget _buildAssetItem({
required BuildContext context,
required IconData icon,
required Color iconColor,
required Color iconBgColor,
required String title,
String? amount,
bool isLoading = false,
String? valueInCny,
String? tag,
String? growthText,
String? badge,
Color? badgeColor,
Color? badgeBgColor,
String? subtitle,
}) {
final isDark = AppColors.isDark(context);
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.cardOf(context),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: isDark ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
child: Row(
children: [
// 图标
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: iconBgColor,
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: iconColor, size: 24),
),
const SizedBox(width: 12),
// 内容
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行
Row(
children: [
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.textPrimaryOf(context),
),
),
if (badge != null) ...[
const SizedBox(width: 7),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: badgeBgColor ?? (isDark ? _green.withOpacity(0.2) : _scandal),
borderRadius: BorderRadius.circular(9999),
),
child: Text(
badge,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
color: badgeColor ?? _jewel,
),
),
),
],
],
),
const SizedBox(height: 2),
// 数量 - 闪烁占位符
DataText(
data: amount != null ? formatAmount(amount) : null,
isLoading: isLoading,
placeholder: '--',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textPrimaryOf(context),
),
),
// 估值
if (valueInCny != null)
DataText(
data: isLoading ? null : '$valueInCny',
isLoading: isLoading,
placeholder: '≈ ¥--',
style: TextStyle(
fontSize: 12,
color: AppColors.textMutedOf(context),
),
),
// 副标题
if (subtitle != null)
Padding(
padding: const EdgeInsets.only(top: 3),
child: Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: AppColors.textMutedOf(context),
),
),
),
// 标签行
if (tag != null || growthText != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
children: [
if (tag != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: isDark ? _orange.withOpacity(0.15) : _serenade,
borderRadius: BorderRadius.circular(12),
),
child: DataText(
data: isLoading ? null : tag,
isLoading: isLoading,
placeholder: '含倍数资产: --',
style: const TextStyle(
fontSize: 10,
color: _orange,
),
),
),
if (growthText != null) ...[
const SizedBox(width: 8),
Row(
children: [
const Icon(Icons.bolt, size: 12, color: _green),
DataText(
data: isLoading ? null : growthText,
isLoading: isLoading,
placeholder: '每秒 +--',
style: const TextStyle(
fontSize: 10,
color: _green,
),
),
],
),
],
],
),
),
],
),
),
// 箭头
Icon(Icons.chevron_right, size: 14, color: AppColors.textMutedOf(context)),
],
),
);
}
Widget _buildEarningsCard(BuildContext context, AssetDisplay? asset, bool isLoading) {
final isDark = AppColors.isDark(context);
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColors.cardOf(context),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: isDark ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
child: Column(
children: [
// 标题
Row(
children: [
Container(
width: 4,
height: 20,
decoration: BoxDecoration(
color: _orange,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
'交易统计',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppColors.textPrimaryOf(context),
),
),
],
),
const SizedBox(height: 16),
// 统计数据
Row(
children: [
_buildEarningsItem(
context,
'累计买入',
asset != null ? formatCompact(asset.totalBought) : null,
_orange,
isLoading,
),
Container(
width: 1,
height: 40,
color: isDark ? AppColors.borderOf(context) : _serenade,
),
_buildEarningsItem(
context,
'累计卖出',
asset != null ? formatCompact(asset.totalSold) : null,
_green,
isLoading,
),
Container(
width: 1,
height: 40,
color: isDark ? AppColors.borderOf(context) : _serenade,
),
_buildEarningsItem(
context,
'销毁倍数',
asset != null ? '${formatDecimal(asset.burnMultiplier, 4)}x' : null,
AppColors.textMutedOf(context),
isLoading,
),
],
),
],
),
);
}
Widget _buildEarningsItem(BuildContext context, String label, String? value, Color valueColor, bool isLoading) {
return Expanded(
child: Column(
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: AppColors.textSecondaryOf(context),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
DataText(
data: value,
isLoading: isLoading,
placeholder: '--',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: valueColor,
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildAccountList(BuildContext context, AssetDisplay? asset, bool isLoading) {
final isDark = AppColors.isDark(context);
return Column(
children: [
// 交易账户(可用现金)
_buildAccountItem(
context: context,
icon: Icons.account_balance_wallet,
iconColor: _orange,
title: '可用积分值',
balance: asset?.availableCash,
isLoading: isLoading,
unit: '积分值',
status: '可交易',
statusColor: _green,
statusBgColor: isDark ? _green.withOpacity(0.15) : _feta,
),
const SizedBox(height: 16),
// 冻结现金
_buildAccountItem(
context: context,
icon: Icons.lock_outline,
iconColor: _orange,
title: '冻结积分值',
balance: asset?.frozenCash,
isLoading: isLoading,
unit: '积分值',
status: '挂单中',
statusColor: AppColors.textMutedOf(context),
statusBgColor: AppColors.cardOf(context),
statusBorder: true,
),
],
);
}
Widget _buildAccountItem({
required BuildContext context,
required IconData icon,
required Color iconColor,
required String title,
String? balance,
bool isLoading = false,
required String unit,
required String status,
required Color statusColor,
required Color statusBgColor,
bool statusBorder = false,
}) {
final isDark = AppColors.isDark(context);
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.cardOf(context),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: isDark ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
child: Row(
children: [
// 图标
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: isDark ? _orange.withOpacity(0.15) : _serenade,
borderRadius: BorderRadius.circular(18),
),
child: Icon(icon, color: iconColor, size: 20),
),
const SizedBox(width: 12),
// 内容
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.textPrimaryOf(context),
),
),
const SizedBox(height: 2),
Row(
children: [
DataText(
data: balance != null ? formatAmount(balance) : null,
isLoading: isLoading,
placeholder: '--',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _orange,
),
),
Text(
' $unit',
style: TextStyle(
fontSize: 12,
color: AppColors.textMutedOf(context),
),
),
],
),
],
),
),
// 状态标签
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: statusBgColor,
borderRadius: BorderRadius.circular(9999),
border: statusBorder ? Border.all(color: AppColors.borderOf(context)) : null,
),
child: Text(
status,
style: TextStyle(
fontSize: 10,
color: statusColor,
),
),
),
const SizedBox(width: 8),
// 箭头
Icon(Icons.chevron_right, size: 14, color: AppColors.textMutedOf(context)),
],
),
);
}
}