refactor: 交易页面改为券+行业分类模式,移除交易对tabs
- market_page: 移除券/法币/数字货币/稳定币tabs,改为行业分类过滤(餐饮/购物/娱乐/出行/生活/运动) - market_page: 新增排序栏(折扣率/价格/到期时间),二级市场改为券名+品牌+行业标签展示 - trading_detail_page: 移除SBUX/USDT交易对概念,改为券信息卡片+配置货币符号 - trading_detail_page: 新增券信息卡片(品牌/行业/信用评级/面值/到期),价格显示折扣率 - 计价货币由用户在"我的→设置"中配置,默认跟随语言 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d9c953149b
commit
003b571f94
|
|
@ -5,8 +5,9 @@ import '../../../../app/theme/app_spacing.dart';
|
||||||
|
|
||||||
/// 交易 - 币安风格交易所
|
/// 交易 - 币安风格交易所
|
||||||
///
|
///
|
||||||
/// 一级市场(打新申购)/ 二级市场(交易所)
|
/// 一级市场(打新申购)/ 二级市场(交易所行情)
|
||||||
/// 支持交易对:券/法币、券/数字货币、券/稳定币
|
/// 行业分类过滤 + 排序 + 券列表
|
||||||
|
/// 计价货币由用户在"我的→设置"中配置,默认跟随语言(中文=¥,英文=$)
|
||||||
class MarketPage extends StatefulWidget {
|
class MarketPage extends StatefulWidget {
|
||||||
const MarketPage({super.key});
|
const MarketPage({super.key});
|
||||||
|
|
||||||
|
|
@ -17,7 +18,8 @@ class MarketPage extends StatefulWidget {
|
||||||
class _MarketPageState extends State<MarketPage>
|
class _MarketPageState extends State<MarketPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late TabController _marketTabController;
|
late TabController _marketTabController;
|
||||||
int _pairTypeIndex = 0; // 0=券/法币, 1=券/数字货币, 2=券/稳定币, 3=收藏
|
String? _selectedCategory; // null = 全部
|
||||||
|
int _sortIndex = 0; // 0=折扣率, 1=价格↑, 2=价格↓, 3=到期时间
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -69,13 +71,104 @@ class _MarketPageState extends State<MarketPage>
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: TabBarView(
|
body: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// 行业分类过滤
|
||||||
|
_buildCategoryFilter(),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// 排序栏
|
||||||
|
_buildSortBar(),
|
||||||
|
const Divider(height: 1),
|
||||||
|
// 内容
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
controller: _marketTabController,
|
controller: _marketTabController,
|
||||||
children: [
|
children: [
|
||||||
_buildPrimaryMarket(),
|
_buildPrimaryMarket(),
|
||||||
_buildSecondaryMarket(),
|
_buildSecondaryMarket(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 行业分类过滤
|
||||||
|
// ============================================================
|
||||||
|
Widget _buildCategoryFilter() {
|
||||||
|
final categories = [
|
||||||
|
(null, '全部'),
|
||||||
|
('dining', '餐饮'),
|
||||||
|
('shopping', '购物'),
|
||||||
|
('entertainment', '娱乐'),
|
||||||
|
('travel', '出行'),
|
||||||
|
('life', '生活'),
|
||||||
|
('sports', '运动'),
|
||||||
|
];
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: ListView.separated(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
itemCount: categories.length,
|
||||||
|
separatorBuilder: (_, __) => const SizedBox(width: 8),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final (key, label) = categories[index];
|
||||||
|
final isSelected = _selectedCategory == key;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => setState(() => _selectedCategory = key),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? AppColors.primary : AppColors.gray50,
|
||||||
|
borderRadius: AppSpacing.borderRadiusFull,
|
||||||
|
border: isSelected
|
||||||
|
? null
|
||||||
|
: Border.all(color: AppColors.borderLight),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: AppTypography.labelSmall.copyWith(
|
||||||
|
color: isSelected ? Colors.white : AppColors.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 排序栏
|
||||||
|
// ============================================================
|
||||||
|
Widget _buildSortBar() {
|
||||||
|
final sorts = ['折扣率', '价格↑', '价格↓', '到期时间'];
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
children: sorts.asMap().entries.map((entry) {
|
||||||
|
final isSelected = _sortIndex == entry.key;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 16),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => setState(() => _sortIndex = entry.key),
|
||||||
|
child: Text(
|
||||||
|
entry.value,
|
||||||
|
style: AppTypography.caption.copyWith(
|
||||||
|
color: isSelected ? AppColors.primary : AppColors.textTertiary,
|
||||||
|
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +177,7 @@ class _MarketPageState extends State<MarketPage>
|
||||||
// ============================================================
|
// ============================================================
|
||||||
Widget _buildPrimaryMarket() {
|
Widget _buildPrimaryMarket() {
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 100),
|
padding: const EdgeInsets.fromLTRB(20, 12, 20, 100),
|
||||||
itemCount: _mockLaunches.length,
|
itemCount: _mockLaunches.length,
|
||||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
|
@ -108,7 +201,7 @@ class _MarketPageState extends State<MarketPage>
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Header: brand + status badge
|
// Header: brand + category + status
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
|
|
@ -126,14 +219,32 @@ class _MarketPageState extends State<MarketPage>
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(launch.brandName, style: AppTypography.labelMedium),
|
Row(
|
||||||
Text(launch.couponName, style: AppTypography.bodySmall),
|
children: [
|
||||||
|
Text(launch.brandName,
|
||||||
|
style: AppTypography.labelMedium),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5, vertical: 1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.gray100,
|
||||||
|
borderRadius: AppSpacing.borderRadiusFull,
|
||||||
|
),
|
||||||
|
child: Text(launch.categoryLabel,
|
||||||
|
style: AppTypography.caption
|
||||||
|
.copyWith(fontSize: 10)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(launch.couponName,
|
||||||
|
style: AppTypography.bodySmall),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: launch.statusColor.withValues(alpha: 0.1),
|
color: launch.statusColor.withValues(alpha: 0.1),
|
||||||
borderRadius: AppSpacing.borderRadiusFull,
|
borderRadius: AppSpacing.borderRadiusFull,
|
||||||
|
|
@ -151,19 +262,24 @@ class _MarketPageState extends State<MarketPage>
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Price + Supply info
|
// Price info
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_buildLaunchInfo('发行价', '\$${launch.issuePrice.toStringAsFixed(2)}'),
|
_buildLaunchInfo('发行价',
|
||||||
_buildLaunchInfo('面值', '\$${launch.faceValue.toStringAsFixed(0)}'),
|
'\$${launch.issuePrice.toStringAsFixed(2)}'),
|
||||||
_buildLaunchInfo('折扣', '${(launch.issuePrice / launch.faceValue * 10).toStringAsFixed(1)}折'),
|
_buildLaunchInfo(
|
||||||
_buildLaunchInfo('发行量', '${launch.totalSupply}'),
|
'面值', '\$${launch.faceValue.toStringAsFixed(0)}'),
|
||||||
|
_buildLaunchInfo(
|
||||||
|
'折扣',
|
||||||
|
'${(launch.issuePrice / launch.faceValue * 10).toStringAsFixed(1)}折'),
|
||||||
|
_buildLaunchInfo(
|
||||||
|
'发行量', '${launch.totalSupply}'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Progress bar
|
// Progress
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -187,8 +303,8 @@ class _MarketPageState extends State<MarketPage>
|
||||||
value: launch.soldPercent,
|
value: launch.soldPercent,
|
||||||
minHeight: 6,
|
minHeight: 6,
|
||||||
backgroundColor: AppColors.gray100,
|
backgroundColor: AppColors.gray100,
|
||||||
valueColor:
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||||
const AlwaysStoppedAnimation<Color>(AppColors.primary),
|
AppColors.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -196,7 +312,6 @@ class _MarketPageState extends State<MarketPage>
|
||||||
|
|
||||||
if (launch.status == 0) ...[
|
if (launch.status == 0) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// Countdown
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.access_time_rounded,
|
const Icon(Icons.access_time_rounded,
|
||||||
|
|
@ -236,26 +351,19 @@ class _MarketPageState extends State<MarketPage>
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 二级市场 - 交易所行情 (Binance style)
|
// 二级市场 - 交易所行情(按行业分类浏览)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
Widget _buildSecondaryMarket() {
|
Widget _buildSecondaryMarket() {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 12),
|
// 表头
|
||||||
// Pair type tabs
|
|
||||||
_buildPairTypeTabs(),
|
|
||||||
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
|
|
||||||
// Column headers
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child:
|
child: Text('券名/品牌', style: AppTypography.caption)),
|
||||||
Text('交易对', style: AppTypography.caption)),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text('最新价',
|
child: Text('最新价',
|
||||||
|
|
@ -269,18 +377,17 @@ class _MarketPageState extends State<MarketPage>
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
|
|
||||||
// Trading pairs list
|
// 券行情列表
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 100),
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 100),
|
||||||
itemCount: _currentPairs.length,
|
itemCount: _mockTradingItems.length,
|
||||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final pair = _currentPairs[index];
|
final item = _mockTradingItems[index];
|
||||||
return _buildTradingPairRow(context, pair);
|
return _buildTradingRow(context, item);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -288,55 +395,18 @@ class _MarketPageState extends State<MarketPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPairTypeTabs() {
|
Widget _buildTradingRow(BuildContext context, _TradingItem item) {
|
||||||
final tabs = ['券/法币', '券/数字货币', '券/稳定币', '收藏'];
|
final isPositive = item.change24h >= 0;
|
||||||
return SizedBox(
|
|
||||||
height: 36,
|
|
||||||
child: ListView.separated(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
itemCount: tabs.length,
|
|
||||||
separatorBuilder: (_, __) => const SizedBox(width: 8),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final isSelected = _pairTypeIndex == index;
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => setState(() => _pairTypeIndex = index),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? AppColors.primary : AppColors.gray50,
|
|
||||||
borderRadius: AppSpacing.borderRadiusFull,
|
|
||||||
border:
|
|
||||||
isSelected ? null : Border.all(color: AppColors.borderLight),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
tabs[index],
|
|
||||||
style: AppTypography.labelSmall.copyWith(
|
|
||||||
color: isSelected ? Colors.white : AppColors.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTradingPairRow(BuildContext context, _TradingPair pair) {
|
|
||||||
final isPositive = pair.change24h >= 0;
|
|
||||||
final changeColor = isPositive ? AppColors.success : AppColors.error;
|
final changeColor = isPositive ? AppColors.success : AppColors.error;
|
||||||
final changePrefix = isPositive ? '+' : '';
|
final changePrefix = isPositive ? '+' : '';
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () => Navigator.pushNamed(context, '/trading/detail'),
|
||||||
Navigator.pushNamed(context, '/trading/detail', arguments: pair);
|
|
||||||
},
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// Trading pair name
|
// 券名 + 品牌 + 行业标签
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -345,63 +415,71 @@ class _MarketPageState extends State<MarketPage>
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
pair.baseName,
|
item.couponName,
|
||||||
style: AppTypography.labelMedium.copyWith(
|
style: AppTypography.labelMedium.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
const SizedBox(width: 4),
|
||||||
' / ${pair.quoteName}',
|
Container(
|
||||||
style: AppTypography.bodySmall.copyWith(
|
padding: const EdgeInsets.symmetric(
|
||||||
color: AppColors.textTertiary,
|
horizontal: 4, vertical: 1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.gray100,
|
||||||
|
borderRadius: AppSpacing.borderRadiusFull,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
item.categoryLabel,
|
||||||
|
style: AppTypography.caption
|
||||||
|
.copyWith(fontSize: 9),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
'Vol ${pair.volume24h}',
|
'${item.brandName} · Vol ${item.volume24h}',
|
||||||
style: AppTypography.caption,
|
style: AppTypography.caption,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Price
|
// 最新价
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
pair.priceDisplay,
|
'\$${item.currentPrice.toStringAsFixed(2)}',
|
||||||
style: AppTypography.labelMedium.copyWith(
|
style: AppTypography.labelMedium.copyWith(
|
||||||
color: changeColor,
|
color: changeColor,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'≈ \$${pair.priceUsd.toStringAsFixed(2)}',
|
'面值 \$${item.faceValue.toStringAsFixed(0)}',
|
||||||
style: AppTypography.caption,
|
style: AppTypography.caption,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 24h Change
|
// 24h涨跌
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
horizontal: 10, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: changeColor,
|
color: changeColor,
|
||||||
borderRadius: AppSpacing.borderRadiusSm,
|
borderRadius: AppSpacing.borderRadiusSm,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'$changePrefix${pair.change24h.toStringAsFixed(2)}%',
|
'$changePrefix${item.change24h.toStringAsFixed(2)}%',
|
||||||
style: AppTypography.labelSmall.copyWith(
|
style: AppTypography.labelSmall.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|
@ -415,21 +493,6 @@ class _MarketPageState extends State<MarketPage>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<_TradingPair> get _currentPairs {
|
|
||||||
switch (_pairTypeIndex) {
|
|
||||||
case 0:
|
|
||||||
return _fiatPairs;
|
|
||||||
case 1:
|
|
||||||
return _cryptoPairs;
|
|
||||||
case 2:
|
|
||||||
return _stablePairs;
|
|
||||||
case 3:
|
|
||||||
return _favoritePairs;
|
|
||||||
default:
|
|
||||||
return _fiatPairs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -438,6 +501,7 @@ class _MarketPageState extends State<MarketPage>
|
||||||
class _LaunchItem {
|
class _LaunchItem {
|
||||||
final String brandName;
|
final String brandName;
|
||||||
final String couponName;
|
final String couponName;
|
||||||
|
final String categoryLabel;
|
||||||
final double issuePrice;
|
final double issuePrice;
|
||||||
final double faceValue;
|
final double faceValue;
|
||||||
final int totalSupply;
|
final int totalSupply;
|
||||||
|
|
@ -448,6 +512,7 @@ class _LaunchItem {
|
||||||
const _LaunchItem({
|
const _LaunchItem({
|
||||||
required this.brandName,
|
required this.brandName,
|
||||||
required this.couponName,
|
required this.couponName,
|
||||||
|
required this.categoryLabel,
|
||||||
required this.issuePrice,
|
required this.issuePrice,
|
||||||
required this.faceValue,
|
required this.faceValue,
|
||||||
required this.totalSupply,
|
required this.totalSupply,
|
||||||
|
|
@ -483,52 +548,44 @@ class _LaunchItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TradingPair {
|
class _TradingItem {
|
||||||
final String baseName;
|
final String couponName;
|
||||||
final String quoteName;
|
final String brandName;
|
||||||
final double price;
|
final String categoryLabel;
|
||||||
final double priceUsd;
|
final double faceValue;
|
||||||
|
final double currentPrice;
|
||||||
final double change24h;
|
final double change24h;
|
||||||
final String volume24h;
|
final String volume24h;
|
||||||
final double high24h;
|
|
||||||
final double low24h;
|
|
||||||
final double open24h;
|
|
||||||
|
|
||||||
const _TradingPair({
|
const _TradingItem({
|
||||||
required this.baseName,
|
required this.couponName,
|
||||||
required this.quoteName,
|
required this.brandName,
|
||||||
required this.price,
|
required this.categoryLabel,
|
||||||
required this.priceUsd,
|
required this.faceValue,
|
||||||
|
required this.currentPrice,
|
||||||
required this.change24h,
|
required this.change24h,
|
||||||
required this.volume24h,
|
required this.volume24h,
|
||||||
required this.high24h,
|
|
||||||
required this.low24h,
|
|
||||||
required this.open24h,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
String get priceDisplay => price >= 1
|
|
||||||
? price.toStringAsFixed(2)
|
|
||||||
: price.toStringAsFixed(6);
|
|
||||||
|
|
||||||
String get pairSymbol => '$baseName/$quoteName';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Mock Data
|
// Mock Data
|
||||||
// ============================================================
|
// ============================================================
|
||||||
final _mockLaunches = [
|
const _mockLaunches = [
|
||||||
const _LaunchItem(
|
_LaunchItem(
|
||||||
brandName: 'Starbucks',
|
brandName: 'Starbucks',
|
||||||
couponName: '星巴克 \$25 礼品卡 2026春季限定',
|
couponName: '星巴克 \$25 礼品卡 2026春季限定',
|
||||||
|
categoryLabel: '餐饮',
|
||||||
issuePrice: 21.25,
|
issuePrice: 21.25,
|
||||||
faceValue: 25.0,
|
faceValue: 25.0,
|
||||||
totalSupply: 10000,
|
totalSupply: 10000,
|
||||||
soldPercent: 0.73,
|
soldPercent: 0.73,
|
||||||
status: 1,
|
status: 1,
|
||||||
),
|
),
|
||||||
const _LaunchItem(
|
_LaunchItem(
|
||||||
brandName: 'Nike',
|
brandName: 'Nike',
|
||||||
couponName: 'Nike \$100 运动券 Air Max 联名',
|
couponName: 'Nike \$100 运动券 Air Max 联名',
|
||||||
|
categoryLabel: '运动',
|
||||||
issuePrice: 82.0,
|
issuePrice: 82.0,
|
||||||
faceValue: 100.0,
|
faceValue: 100.0,
|
||||||
totalSupply: 5000,
|
totalSupply: 5000,
|
||||||
|
|
@ -536,110 +593,109 @@ final _mockLaunches = [
|
||||||
status: 0,
|
status: 0,
|
||||||
countdown: '2天 14:30:00',
|
countdown: '2天 14:30:00',
|
||||||
),
|
),
|
||||||
const _LaunchItem(
|
_LaunchItem(
|
||||||
brandName: 'Amazon',
|
brandName: 'Amazon',
|
||||||
couponName: 'Amazon \$50 购物券 Prime专属',
|
couponName: 'Amazon \$50 购物券 Prime专属',
|
||||||
|
categoryLabel: '购物',
|
||||||
issuePrice: 42.5,
|
issuePrice: 42.5,
|
||||||
faceValue: 50.0,
|
faceValue: 50.0,
|
||||||
totalSupply: 20000,
|
totalSupply: 20000,
|
||||||
soldPercent: 1.0,
|
soldPercent: 1.0,
|
||||||
status: 2,
|
status: 2,
|
||||||
),
|
),
|
||||||
const _LaunchItem(
|
_LaunchItem(
|
||||||
brandName: 'Walmart',
|
brandName: 'Walmart',
|
||||||
couponName: 'Walmart \$30 生活券',
|
couponName: 'Walmart \$30 生活券',
|
||||||
|
categoryLabel: '生活',
|
||||||
issuePrice: 24.0,
|
issuePrice: 24.0,
|
||||||
faceValue: 30.0,
|
faceValue: 30.0,
|
||||||
totalSupply: 15000,
|
totalSupply: 15000,
|
||||||
soldPercent: 0.45,
|
soldPercent: 0.45,
|
||||||
status: 1,
|
status: 1,
|
||||||
),
|
),
|
||||||
];
|
_LaunchItem(
|
||||||
|
brandName: 'AMC',
|
||||||
// 券/法币 trading pairs
|
couponName: 'AMC \$20 电影券 IMAX场',
|
||||||
final _fiatPairs = [
|
categoryLabel: '娱乐',
|
||||||
const _TradingPair(
|
issuePrice: 16.0,
|
||||||
baseName: 'SBUX', quoteName: 'USD',
|
faceValue: 20.0,
|
||||||
price: 21.35, priceUsd: 21.35, change24h: 2.15,
|
totalSupply: 8000,
|
||||||
volume24h: '125.3K', high24h: 21.80, low24h: 20.90, open24h: 20.90,
|
soldPercent: 0.88,
|
||||||
),
|
status: 1,
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'AMZN', quoteName: 'USD',
|
|
||||||
price: 85.20, priceUsd: 85.20, change24h: -1.23,
|
|
||||||
volume24h: '89.7K', high24h: 87.50, low24h: 84.10, open24h: 86.26,
|
|
||||||
),
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'NIKE', quoteName: 'USD',
|
|
||||||
price: 68.50, priceUsd: 68.50, change24h: 5.32,
|
|
||||||
volume24h: '234.1K', high24h: 69.20, low24h: 65.00, open24h: 65.04,
|
|
||||||
),
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'TGT', quoteName: 'CNY',
|
|
||||||
price: 168.80, priceUsd: 24.00, change24h: -0.56,
|
|
||||||
volume24h: '45.2K', high24h: 170.50, low24h: 167.00, open24h: 169.75,
|
|
||||||
),
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'WMT', quoteName: 'USD',
|
|
||||||
price: 42.30, priceUsd: 42.30, change24h: 1.87,
|
|
||||||
volume24h: '67.8K', high24h: 43.00, low24h: 41.50, open24h: 41.52,
|
|
||||||
),
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'COST', quoteName: 'USD',
|
|
||||||
price: 38.75, priceUsd: 38.75, change24h: 3.41,
|
|
||||||
volume24h: '156.2K', high24h: 39.50, low24h: 37.40, open24h: 37.47,
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 券/数字货币 trading pairs
|
const _mockTradingItems = [
|
||||||
final _cryptoPairs = [
|
_TradingItem(
|
||||||
const _TradingPair(
|
couponName: '星巴克\$25',
|
||||||
baseName: 'SBUX', quoteName: 'BTC',
|
brandName: 'Starbucks',
|
||||||
price: 0.000215, priceUsd: 21.35, change24h: 1.85,
|
categoryLabel: '餐饮',
|
||||||
volume24h: '12.5K', high24h: 0.000220, low24h: 0.000210, open24h: 0.000211,
|
faceValue: 25.0,
|
||||||
|
currentPrice: 21.35,
|
||||||
|
change24h: 2.15,
|
||||||
|
volume24h: '125.3K',
|
||||||
),
|
),
|
||||||
const _TradingPair(
|
_TradingItem(
|
||||||
baseName: 'AMZN', quoteName: 'ETH',
|
couponName: 'Amazon\$100',
|
||||||
price: 0.0234, priceUsd: 85.20, change24h: -2.10,
|
brandName: 'Amazon',
|
||||||
volume24h: '8.3K', high24h: 0.0240, low24h: 0.0230, open24h: 0.0239,
|
categoryLabel: '购物',
|
||||||
|
faceValue: 100.0,
|
||||||
|
currentPrice: 85.20,
|
||||||
|
change24h: -1.23,
|
||||||
|
volume24h: '89.7K',
|
||||||
),
|
),
|
||||||
const _TradingPair(
|
_TradingItem(
|
||||||
baseName: 'NIKE', quoteName: 'BTC',
|
couponName: 'Nike\$80',
|
||||||
price: 0.000690, priceUsd: 68.50, change24h: 4.56,
|
brandName: 'Nike',
|
||||||
volume24h: '15.7K', high24h: 0.000700, low24h: 0.000660, open24h: 0.000660,
|
categoryLabel: '运动',
|
||||||
|
faceValue: 80.0,
|
||||||
|
currentPrice: 68.50,
|
||||||
|
change24h: 5.32,
|
||||||
|
volume24h: '234.1K',
|
||||||
|
),
|
||||||
|
_TradingItem(
|
||||||
|
couponName: 'Target\$30',
|
||||||
|
brandName: 'Target',
|
||||||
|
categoryLabel: '购物',
|
||||||
|
faceValue: 30.0,
|
||||||
|
currentPrice: 24.00,
|
||||||
|
change24h: -0.56,
|
||||||
|
volume24h: '45.2K',
|
||||||
|
),
|
||||||
|
_TradingItem(
|
||||||
|
couponName: 'Walmart\$50',
|
||||||
|
brandName: 'Walmart',
|
||||||
|
categoryLabel: '生活',
|
||||||
|
faceValue: 50.0,
|
||||||
|
currentPrice: 42.30,
|
||||||
|
change24h: 1.87,
|
||||||
|
volume24h: '67.8K',
|
||||||
|
),
|
||||||
|
_TradingItem(
|
||||||
|
couponName: 'Costco\$40',
|
||||||
|
brandName: 'Costco',
|
||||||
|
categoryLabel: '购物',
|
||||||
|
faceValue: 40.0,
|
||||||
|
currentPrice: 33.60,
|
||||||
|
change24h: 3.41,
|
||||||
|
volume24h: '156.2K',
|
||||||
|
),
|
||||||
|
_TradingItem(
|
||||||
|
couponName: 'AMC\$20',
|
||||||
|
brandName: 'AMC',
|
||||||
|
categoryLabel: '娱乐',
|
||||||
|
faceValue: 20.0,
|
||||||
|
currentPrice: 16.80,
|
||||||
|
change24h: -2.10,
|
||||||
|
volume24h: '78.5K',
|
||||||
|
),
|
||||||
|
_TradingItem(
|
||||||
|
couponName: 'Uber\$25',
|
||||||
|
brandName: 'Uber',
|
||||||
|
categoryLabel: '出行',
|
||||||
|
faceValue: 25.0,
|
||||||
|
currentPrice: 21.00,
|
||||||
|
change24h: 0.95,
|
||||||
|
volume24h: '34.1K',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 券/稳定币 trading pairs
|
|
||||||
final _stablePairs = [
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'SBUX', quoteName: 'USDT',
|
|
||||||
price: 21.30, priceUsd: 21.30, change24h: 2.05,
|
|
||||||
volume24h: '342.5K', high24h: 21.75, low24h: 20.85, open24h: 20.87,
|
|
||||||
),
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'AMZN', quoteName: 'USDT',
|
|
||||||
price: 85.10, priceUsd: 85.10, change24h: -1.35,
|
|
||||||
volume24h: '189.2K', high24h: 87.40, low24h: 84.00, open24h: 86.26,
|
|
||||||
),
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'NIKE', quoteName: 'USDC',
|
|
||||||
price: 68.45, priceUsd: 68.45, change24h: 5.20,
|
|
||||||
volume24h: '278.9K', high24h: 69.10, low24h: 64.90, open24h: 65.07,
|
|
||||||
),
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'WMT', quoteName: 'USDT',
|
|
||||||
price: 42.25, priceUsd: 42.25, change24h: 1.92,
|
|
||||||
volume24h: '98.4K', high24h: 42.90, low24h: 41.45, open24h: 41.45,
|
|
||||||
),
|
|
||||||
const _TradingPair(
|
|
||||||
baseName: 'TGT', quoteName: 'USDC',
|
|
||||||
price: 24.10, priceUsd: 24.10, change24h: -0.41,
|
|
||||||
volume24h: '34.1K', high24h: 24.50, low24h: 23.80, open24h: 24.20,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Favorites
|
|
||||||
final _favoritePairs = [
|
|
||||||
_stablePairs[0], // SBUX/USDT
|
|
||||||
_fiatPairs[2], // NIKE/USD
|
|
||||||
];
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ import 'package:flutter/material.dart';
|
||||||
import '../../../../app/theme/app_colors.dart';
|
import '../../../../app/theme/app_colors.dart';
|
||||||
import '../../../../app/theme/app_typography.dart';
|
import '../../../../app/theme/app_typography.dart';
|
||||||
import '../../../../app/theme/app_spacing.dart';
|
import '../../../../app/theme/app_spacing.dart';
|
||||||
import '../../../../shared/widgets/genex_button.dart';
|
|
||||||
|
|
||||||
/// 交易对详情页 - 币安风格
|
/// 券交易详情页 - 币安风格
|
||||||
///
|
///
|
||||||
/// K线图 + OHLC + 交易深度 + 买卖盘口 + 下单
|
/// 券信息卡片 + K线图 + OHLC + 交易深度 + 买卖盘口 + 下单
|
||||||
|
/// 计价货币由用户在"我的→设置"中配置,默认跟随语言
|
||||||
class TradingDetailPage extends StatefulWidget {
|
class TradingDetailPage extends StatefulWidget {
|
||||||
const TradingDetailPage({super.key});
|
const TradingDetailPage({super.key});
|
||||||
|
|
||||||
|
|
@ -22,6 +22,21 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
String _orderType = 'limit'; // limit, market
|
String _orderType = 'limit'; // limit, market
|
||||||
late TabController _bottomTabController;
|
late TabController _bottomTabController;
|
||||||
|
|
||||||
|
// Mock coupon data (实际从路由参数或状态管理获取)
|
||||||
|
final _coupon = const _CouponInfo(
|
||||||
|
couponName: '星巴克\$25 礼品卡',
|
||||||
|
brandName: 'Starbucks',
|
||||||
|
categoryLabel: '餐饮',
|
||||||
|
faceValue: 25.0,
|
||||||
|
currentPrice: 21.30,
|
||||||
|
change24h: 2.05,
|
||||||
|
creditRating: 'AAA',
|
||||||
|
expiryDate: '2026/06/30',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 计价货币符号 (从用户设置读取)
|
||||||
|
final String _currencySymbol = '\$';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -38,14 +53,14 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('SBUX / USDT'),
|
title: Text(_coupon.couponName),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.star_border_rounded, size: 22),
|
icon: const Icon(Icons.star_border_rounded, size: 22),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.more_horiz_rounded, size: 22),
|
icon: const Icon(Icons.share_rounded, size: 22),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -57,6 +72,9 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
// Coupon info card
|
||||||
|
_buildCouponInfoCard(),
|
||||||
|
|
||||||
// Price header
|
// Price header
|
||||||
_buildPriceHeader(),
|
_buildPriceHeader(),
|
||||||
|
|
||||||
|
|
@ -94,12 +112,93 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coupon Info Card - 券基本信息
|
||||||
|
// ============================================================
|
||||||
|
Widget _buildCouponInfoCard() {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.fromLTRB(20, 8, 20, 0),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.primarySurface,
|
||||||
|
borderRadius: AppSpacing.borderRadiusMd,
|
||||||
|
border: Border.all(color: AppColors.primary.withValues(alpha: 0.15)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Icon
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.surface,
|
||||||
|
borderRadius: AppSpacing.borderRadiusSm,
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.confirmation_number_outlined,
|
||||||
|
color: AppColors.primary, size: 20),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
// Info
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(_coupon.brandName,
|
||||||
|
style: AppTypography.labelMedium),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5, vertical: 1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.gray100,
|
||||||
|
borderRadius: AppSpacing.borderRadiusFull,
|
||||||
|
),
|
||||||
|
child: Text(_coupon.categoryLabel,
|
||||||
|
style:
|
||||||
|
AppTypography.caption.copyWith(fontSize: 10)),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 5, vertical: 1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.success.withValues(alpha: 0.1),
|
||||||
|
borderRadius: AppSpacing.borderRadiusFull,
|
||||||
|
),
|
||||||
|
child: Text(_coupon.creditRating,
|
||||||
|
style: AppTypography.caption.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
|
color: AppColors.success,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'面值 $_currencySymbol${_coupon.faceValue.toStringAsFixed(0)} · 到期 ${_coupon.expiryDate}',
|
||||||
|
style: AppTypography.caption,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Price Header - OHLC + 24h stats
|
// Price Header - OHLC + 24h stats
|
||||||
// ============================================================
|
// ============================================================
|
||||||
Widget _buildPriceHeader() {
|
Widget _buildPriceHeader() {
|
||||||
|
final isPositive = _coupon.change24h >= 0;
|
||||||
|
final changeColor = isPositive ? AppColors.success : AppColors.error;
|
||||||
|
final changePrefix = isPositive ? '+' : '';
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -108,15 +207,15 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'21.30',
|
'$_currencySymbol${_coupon.currentPrice.toStringAsFixed(2)}',
|
||||||
style: AppTypography.priceLarge.copyWith(
|
style: AppTypography.priceLarge.copyWith(
|
||||||
color: AppColors.success,
|
color: changeColor,
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'≈ \$21.30',
|
'折扣 ${(_coupon.currentPrice / _coupon.faceValue * 10).toStringAsFixed(1)}折',
|
||||||
style: AppTypography.bodySmall.copyWith(
|
style: AppTypography.bodySmall.copyWith(
|
||||||
color: AppColors.textTertiary,
|
color: AppColors.textTertiary,
|
||||||
),
|
),
|
||||||
|
|
@ -126,11 +225,11 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.success,
|
color: changeColor,
|
||||||
borderRadius: AppSpacing.borderRadiusSm,
|
borderRadius: AppSpacing.borderRadiusSm,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'+2.05%',
|
'$changePrefix${_coupon.change24h.toStringAsFixed(2)}%',
|
||||||
style: AppTypography.labelSmall.copyWith(
|
style: AppTypography.labelSmall.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|
@ -145,9 +244,9 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
// 24h OHLC stats
|
// 24h OHLC stats
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_buildOhlcStat('24h高', '21.75', AppColors.error),
|
_buildOhlcStat('24h高', '${_currencySymbol}21.75', AppColors.error),
|
||||||
_buildOhlcStat('24h低', '20.85', AppColors.success),
|
_buildOhlcStat('24h低', '${_currencySymbol}20.85', AppColors.success),
|
||||||
_buildOhlcStat('开盘', '20.87', AppColors.textPrimary),
|
_buildOhlcStat('开盘', '${_currencySymbol}20.87', AppColors.textPrimary),
|
||||||
_buildOhlcStat('24h量', '342.5K', AppColors.textPrimary),
|
_buildOhlcStat('24h量', '342.5K', AppColors.textPrimary),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -199,7 +298,7 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
top: 8,
|
top: 8,
|
||||||
left: 12,
|
left: 12,
|
||||||
child: Text(
|
child: Text(
|
||||||
'SBUX/USDT · $_selectedPeriod',
|
'${_coupon.couponName} · $_selectedPeriod',
|
||||||
style: AppTypography.caption.copyWith(
|
style: AppTypography.caption.copyWith(
|
||||||
color: AppColors.textTertiary,
|
color: AppColors.textTertiary,
|
||||||
),
|
),
|
||||||
|
|
@ -291,7 +390,7 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(label, style: AppTypography.caption.copyWith(color: color)),
|
Text(label, style: AppTypography.caption.copyWith(color: color)),
|
||||||
Text('数量', style: AppTypography.caption),
|
Text('数量(张)', style: AppTypography.caption),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|
@ -322,7 +421,7 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
prices[i].toStringAsFixed(2),
|
'$_currencySymbol${prices[i].toStringAsFixed(2)}',
|
||||||
style: AppTypography.caption.copyWith(
|
style: AppTypography.caption.copyWith(
|
||||||
color: color,
|
color: color,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|
@ -427,7 +526,7 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
|
|
||||||
// Price input (hidden for market orders)
|
// Price input (hidden for market orders)
|
||||||
if (_orderType == 'limit') ...[
|
if (_orderType == 'limit') ...[
|
||||||
_buildInputField('价格', '21.30', 'USDT'),
|
_buildInputField('价格', '21.30', _currencySymbol),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -457,7 +556,9 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
children: [
|
children: [
|
||||||
Text('可用', style: AppTypography.caption),
|
Text('可用', style: AppTypography.caption),
|
||||||
Text(
|
Text(
|
||||||
_isBuy ? '1,234.56 USDT' : '3 张 SBUX',
|
_isBuy
|
||||||
|
? '${_currencySymbol}1,234.56'
|
||||||
|
: '3 张 ${_coupon.couponName}',
|
||||||
style: AppTypography.caption.copyWith(
|
style: AppTypography.caption.copyWith(
|
||||||
color: AppColors.textPrimary,
|
color: AppColors.textPrimary,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|
@ -483,7 +584,7 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
_isBuy ? '买入 SBUX' : '卖出 SBUX',
|
_isBuy ? '买入 ${_coupon.couponName}' : '卖出 ${_coupon.couponName}',
|
||||||
style: AppTypography.labelLarge.copyWith(color: Colors.white),
|
style: AppTypography.labelLarge.copyWith(color: Colors.white),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -605,17 +706,17 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Mock orders
|
// Mock orders
|
||||||
_buildOrderItem('买入', 'SBUX/USDT', '21.20', '5', '限价',
|
_buildOrderItem(
|
||||||
AppColors.success),
|
'买入', _coupon.couponName, '21.20', '5', '限价', AppColors.success),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
_buildOrderItem('卖出', 'SBUX/USDT', '21.50', '2', '限价',
|
_buildOrderItem(
|
||||||
AppColors.error),
|
'卖出', _coupon.couponName, '21.50', '2', '限价', AppColors.error),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOrderItem(String side, String pair, String price,
|
Widget _buildOrderItem(String side, String couponName, String price,
|
||||||
String amount, String type, Color sideColor) {
|
String amount, String type, Color sideColor) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
|
@ -640,8 +741,8 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(pair, style: AppTypography.labelSmall),
|
Text(couponName, style: AppTypography.labelSmall),
|
||||||
Text('$type · $amount张 @ $price',
|
Text('$type · ${amount}张 @ $_currencySymbol$price',
|
||||||
style: AppTypography.caption),
|
style: AppTypography.caption),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -720,6 +821,31 @@ class _TradingDetailPageState extends State<TradingDetailPage>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Data Model
|
||||||
|
// ============================================================
|
||||||
|
class _CouponInfo {
|
||||||
|
final String couponName;
|
||||||
|
final String brandName;
|
||||||
|
final String categoryLabel;
|
||||||
|
final double faceValue;
|
||||||
|
final double currentPrice;
|
||||||
|
final double change24h;
|
||||||
|
final String creditRating;
|
||||||
|
final String expiryDate;
|
||||||
|
|
||||||
|
const _CouponInfo({
|
||||||
|
required this.couponName,
|
||||||
|
required this.brandName,
|
||||||
|
required this.categoryLabel,
|
||||||
|
required this.faceValue,
|
||||||
|
required this.currentPrice,
|
||||||
|
required this.change24h,
|
||||||
|
required this.creditRating,
|
||||||
|
required this.expiryDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Mock Candlestick Chart Painter
|
// Mock Candlestick Chart Painter
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue