From 003b571f94c97195573be5e65ca54608b560e564 Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 11 Feb 2026 20:55:24 -0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BA=A4=E6=98=93=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=94=B9=E4=B8=BA=E5=88=B8+=E8=A1=8C=E4=B8=9A?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E6=A8=A1=E5=BC=8F=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E5=AF=B9tabs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - market_page: 移除券/法币/数字货币/稳定币tabs,改为行业分类过滤(餐饮/购物/娱乐/出行/生活/运动) - market_page: 新增排序栏(折扣率/价格/到期时间),二级市场改为券名+品牌+行业标签展示 - trading_detail_page: 移除SBUX/USDT交易对概念,改为券信息卡片+配置货币符号 - trading_detail_page: 新增券信息卡片(品牌/行业/信用评级/面值/到期),价格显示折扣率 - 计价货币由用户在"我的→设置"中配置,默认跟随语言 Co-Authored-By: Claude Opus 4.6 --- .../presentation/pages/market_page.dart | 486 ++++++++++-------- .../pages/trading_detail_page.dart | 180 ++++++- 2 files changed, 424 insertions(+), 242 deletions(-) diff --git a/frontend/genex-mobile/lib/features/coupons/presentation/pages/market_page.dart b/frontend/genex-mobile/lib/features/coupons/presentation/pages/market_page.dart index 80dc664..312eabc 100644 --- a/frontend/genex-mobile/lib/features/coupons/presentation/pages/market_page.dart +++ b/frontend/genex-mobile/lib/features/coupons/presentation/pages/market_page.dart @@ -5,8 +5,9 @@ import '../../../../app/theme/app_spacing.dart'; /// 交易 - 币安风格交易所 /// -/// 一级市场(打新申购)/ 二级市场(交易所) -/// 支持交易对:券/法币、券/数字货币、券/稳定币 +/// 一级市场(打新申购)/ 二级市场(交易所行情) +/// 行业分类过滤 + 排序 + 券列表 +/// 计价货币由用户在"我的→设置"中配置,默认跟随语言(中文=¥,英文=$) class MarketPage extends StatefulWidget { const MarketPage({super.key}); @@ -17,7 +18,8 @@ class MarketPage extends StatefulWidget { class _MarketPageState extends State with SingleTickerProviderStateMixin { late TabController _marketTabController; - int _pairTypeIndex = 0; // 0=券/法币, 1=券/数字货币, 2=券/稳定币, 3=收藏 + String? _selectedCategory; // null = 全部 + int _sortIndex = 0; // 0=折扣率, 1=价格↑, 2=价格↓, 3=到期时间 @override void initState() { @@ -69,22 +71,113 @@ class _MarketPageState extends State ), ), ), - body: TabBarView( - controller: _marketTabController, + body: Column( children: [ - _buildPrimaryMarket(), - _buildSecondaryMarket(), + const SizedBox(height: 12), + // 行业分类过滤 + _buildCategoryFilter(), + const SizedBox(height: 8), + // 排序栏 + _buildSortBar(), + const Divider(height: 1), + // 内容 + Expanded( + child: TabBarView( + controller: _marketTabController, + children: [ + _buildPrimaryMarket(), + _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(), + ), + ); + } + // ============================================================ // 一级市场 - 打新申购 (Launchpad style) // ============================================================ Widget _buildPrimaryMarket() { return ListView.separated( - padding: const EdgeInsets.fromLTRB(20, 16, 20, 100), + padding: const EdgeInsets.fromLTRB(20, 12, 20, 100), itemCount: _mockLaunches.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, index) { @@ -108,7 +201,7 @@ class _MarketPageState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Header: brand + status badge + // Header: brand + category + status Row( children: [ Container( @@ -126,14 +219,32 @@ class _MarketPageState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(launch.brandName, style: AppTypography.labelMedium), - Text(launch.couponName, style: AppTypography.bodySmall), + Row( + 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( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), decoration: BoxDecoration( color: launch.statusColor.withValues(alpha: 0.1), borderRadius: AppSpacing.borderRadiusFull, @@ -151,19 +262,24 @@ class _MarketPageState extends State const SizedBox(height: 16), - // Price + Supply info + // Price info Row( children: [ - _buildLaunchInfo('发行价', '\$${launch.issuePrice.toStringAsFixed(2)}'), - _buildLaunchInfo('面值', '\$${launch.faceValue.toStringAsFixed(0)}'), - _buildLaunchInfo('折扣', '${(launch.issuePrice / launch.faceValue * 10).toStringAsFixed(1)}折'), - _buildLaunchInfo('发行量', '${launch.totalSupply}'), + _buildLaunchInfo('发行价', + '\$${launch.issuePrice.toStringAsFixed(2)}'), + _buildLaunchInfo( + '面值', '\$${launch.faceValue.toStringAsFixed(0)}'), + _buildLaunchInfo( + '折扣', + '${(launch.issuePrice / launch.faceValue * 10).toStringAsFixed(1)}折'), + _buildLaunchInfo( + '发行量', '${launch.totalSupply}'), ], ), const SizedBox(height: 12), - // Progress bar + // Progress Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -187,8 +303,8 @@ class _MarketPageState extends State value: launch.soldPercent, minHeight: 6, backgroundColor: AppColors.gray100, - valueColor: - const AlwaysStoppedAnimation(AppColors.primary), + valueColor: const AlwaysStoppedAnimation( + AppColors.primary), ), ), ], @@ -196,7 +312,6 @@ class _MarketPageState extends State if (launch.status == 0) ...[ const SizedBox(height: 12), - // Countdown Row( children: [ const Icon(Icons.access_time_rounded, @@ -236,26 +351,19 @@ class _MarketPageState extends State } // ============================================================ - // 二级市场 - 交易所行情 (Binance style) + // 二级市场 - 交易所行情(按行业分类浏览) // ============================================================ Widget _buildSecondaryMarket() { return Column( children: [ - const SizedBox(height: 12), - // Pair type tabs - _buildPairTypeTabs(), - - const SizedBox(height: 8), - - // Column headers + // 表头 Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: Row( children: [ Expanded( flex: 3, - child: - Text('交易对', style: AppTypography.caption)), + child: Text('券名/品牌', style: AppTypography.caption)), Expanded( flex: 2, child: Text('最新价', @@ -269,18 +377,17 @@ class _MarketPageState extends State ], ), ), - const Divider(height: 1), - // Trading pairs list + // 券行情列表 Expanded( child: ListView.separated( padding: const EdgeInsets.fromLTRB(20, 0, 20, 100), - itemCount: _currentPairs.length, + itemCount: _mockTradingItems.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (context, index) { - final pair = _currentPairs[index]; - return _buildTradingPairRow(context, pair); + final item = _mockTradingItems[index]; + return _buildTradingRow(context, item); }, ), ), @@ -288,55 +395,18 @@ class _MarketPageState extends State ); } - Widget _buildPairTypeTabs() { - final tabs = ['券/法币', '券/数字货币', '券/稳定币', '收藏']; - 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; + Widget _buildTradingRow(BuildContext context, _TradingItem item) { + final isPositive = item.change24h >= 0; final changeColor = isPositive ? AppColors.success : AppColors.error; final changePrefix = isPositive ? '+' : ''; return InkWell( - onTap: () { - Navigator.pushNamed(context, '/trading/detail', arguments: pair); - }, + onTap: () => Navigator.pushNamed(context, '/trading/detail'), child: Padding( padding: const EdgeInsets.symmetric(vertical: 14), child: Row( children: [ - // Trading pair name + // 券名 + 品牌 + 行业标签 Expanded( flex: 3, child: Column( @@ -345,63 +415,71 @@ class _MarketPageState extends State Row( children: [ Text( - pair.baseName, + item.couponName, style: AppTypography.labelMedium.copyWith( fontWeight: FontWeight.w600, ), ), - Text( - ' / ${pair.quoteName}', - style: AppTypography.bodySmall.copyWith( - color: AppColors.textTertiary, + const SizedBox(width: 4), + Container( + padding: const EdgeInsets.symmetric( + 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), Text( - 'Vol ${pair.volume24h}', + '${item.brandName} · Vol ${item.volume24h}', style: AppTypography.caption, ), ], ), ), - // Price + // 最新价 Expanded( flex: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - pair.priceDisplay, + '\$${item.currentPrice.toStringAsFixed(2)}', style: AppTypography.labelMedium.copyWith( color: changeColor, fontWeight: FontWeight.w600, ), ), Text( - '≈ \$${pair.priceUsd.toStringAsFixed(2)}', + '面值 \$${item.faceValue.toStringAsFixed(0)}', style: AppTypography.caption, ), ], ), ), - // 24h Change + // 24h涨跌 Expanded( flex: 2, child: Align( alignment: Alignment.centerRight, child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 6), decoration: BoxDecoration( color: changeColor, borderRadius: AppSpacing.borderRadiusSm, ), child: Text( - '$changePrefix${pair.change24h.toStringAsFixed(2)}%', + '$changePrefix${item.change24h.toStringAsFixed(2)}%', style: AppTypography.labelSmall.copyWith( color: Colors.white, fontWeight: FontWeight.w600, @@ -415,21 +493,6 @@ class _MarketPageState extends State ), ); } - - 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 class _LaunchItem { final String brandName; final String couponName; + final String categoryLabel; final double issuePrice; final double faceValue; final int totalSupply; @@ -448,6 +512,7 @@ class _LaunchItem { const _LaunchItem({ required this.brandName, required this.couponName, + required this.categoryLabel, required this.issuePrice, required this.faceValue, required this.totalSupply, @@ -483,52 +548,44 @@ class _LaunchItem { } } -class _TradingPair { - final String baseName; - final String quoteName; - final double price; - final double priceUsd; +class _TradingItem { + final String couponName; + final String brandName; + final String categoryLabel; + final double faceValue; + final double currentPrice; final double change24h; final String volume24h; - final double high24h; - final double low24h; - final double open24h; - const _TradingPair({ - required this.baseName, - required this.quoteName, - required this.price, - required this.priceUsd, + const _TradingItem({ + required this.couponName, + required this.brandName, + required this.categoryLabel, + required this.faceValue, + required this.currentPrice, required this.change24h, 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 // ============================================================ -final _mockLaunches = [ - const _LaunchItem( +const _mockLaunches = [ + _LaunchItem( brandName: 'Starbucks', couponName: '星巴克 \$25 礼品卡 2026春季限定', + categoryLabel: '餐饮', issuePrice: 21.25, faceValue: 25.0, totalSupply: 10000, soldPercent: 0.73, status: 1, ), - const _LaunchItem( + _LaunchItem( brandName: 'Nike', couponName: 'Nike \$100 运动券 Air Max 联名', + categoryLabel: '运动', issuePrice: 82.0, faceValue: 100.0, totalSupply: 5000, @@ -536,110 +593,109 @@ final _mockLaunches = [ status: 0, countdown: '2天 14:30:00', ), - const _LaunchItem( + _LaunchItem( brandName: 'Amazon', couponName: 'Amazon \$50 购物券 Prime专属', + categoryLabel: '购物', issuePrice: 42.5, faceValue: 50.0, totalSupply: 20000, soldPercent: 1.0, status: 2, ), - const _LaunchItem( + _LaunchItem( brandName: 'Walmart', couponName: 'Walmart \$30 生活券', + categoryLabel: '生活', issuePrice: 24.0, faceValue: 30.0, totalSupply: 15000, soldPercent: 0.45, status: 1, ), -]; - -// 券/法币 trading pairs -final _fiatPairs = [ - const _TradingPair( - baseName: 'SBUX', quoteName: 'USD', - price: 21.35, priceUsd: 21.35, change24h: 2.15, - volume24h: '125.3K', high24h: 21.80, low24h: 20.90, open24h: 20.90, - ), - 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, + _LaunchItem( + brandName: 'AMC', + couponName: 'AMC \$20 电影券 IMAX场', + categoryLabel: '娱乐', + issuePrice: 16.0, + faceValue: 20.0, + totalSupply: 8000, + soldPercent: 0.88, + status: 1, ), ]; -// 券/数字货币 trading pairs -final _cryptoPairs = [ - const _TradingPair( - baseName: 'SBUX', quoteName: 'BTC', - price: 0.000215, priceUsd: 21.35, change24h: 1.85, - volume24h: '12.5K', high24h: 0.000220, low24h: 0.000210, open24h: 0.000211, +const _mockTradingItems = [ + _TradingItem( + couponName: '星巴克\$25', + brandName: 'Starbucks', + categoryLabel: '餐饮', + faceValue: 25.0, + currentPrice: 21.35, + change24h: 2.15, + volume24h: '125.3K', ), - const _TradingPair( - baseName: 'AMZN', quoteName: 'ETH', - price: 0.0234, priceUsd: 85.20, change24h: -2.10, - volume24h: '8.3K', high24h: 0.0240, low24h: 0.0230, open24h: 0.0239, + _TradingItem( + couponName: 'Amazon\$100', + brandName: 'Amazon', + categoryLabel: '购物', + faceValue: 100.0, + currentPrice: 85.20, + change24h: -1.23, + volume24h: '89.7K', ), - const _TradingPair( - baseName: 'NIKE', quoteName: 'BTC', - price: 0.000690, priceUsd: 68.50, change24h: 4.56, - volume24h: '15.7K', high24h: 0.000700, low24h: 0.000660, open24h: 0.000660, + _TradingItem( + couponName: 'Nike\$80', + brandName: 'Nike', + 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 -]; diff --git a/frontend/genex-mobile/lib/features/trading/presentation/pages/trading_detail_page.dart b/frontend/genex-mobile/lib/features/trading/presentation/pages/trading_detail_page.dart index d7e983c..197ed21 100644 --- a/frontend/genex-mobile/lib/features/trading/presentation/pages/trading_detail_page.dart +++ b/frontend/genex-mobile/lib/features/trading/presentation/pages/trading_detail_page.dart @@ -3,11 +3,11 @@ import 'package:flutter/material.dart'; import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_typography.dart'; import '../../../../app/theme/app_spacing.dart'; -import '../../../../shared/widgets/genex_button.dart'; -/// 交易对详情页 - 币安风格 +/// 券交易详情页 - 币安风格 /// -/// K线图 + OHLC + 交易深度 + 买卖盘口 + 下单 +/// 券信息卡片 + K线图 + OHLC + 交易深度 + 买卖盘口 + 下单 +/// 计价货币由用户在"我的→设置"中配置,默认跟随语言 class TradingDetailPage extends StatefulWidget { const TradingDetailPage({super.key}); @@ -22,6 +22,21 @@ class _TradingDetailPageState extends State String _orderType = 'limit'; // limit, market 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 void initState() { super.initState(); @@ -38,14 +53,14 @@ class _TradingDetailPageState extends State Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('SBUX / USDT'), + title: Text(_coupon.couponName), actions: [ IconButton( icon: const Icon(Icons.star_border_rounded, size: 22), onPressed: () {}, ), IconButton( - icon: const Icon(Icons.more_horiz_rounded, size: 22), + icon: const Icon(Icons.share_rounded, size: 22), onPressed: () {}, ), ], @@ -57,6 +72,9 @@ class _TradingDetailPageState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Coupon info card + _buildCouponInfoCard(), + // Price header _buildPriceHeader(), @@ -94,12 +112,93 @@ class _TradingDetailPageState extends State ); } + // ============================================================ + // 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 // ============================================================ Widget _buildPriceHeader() { + final isPositive = _coupon.change24h >= 0; + final changeColor = isPositive ? AppColors.success : AppColors.error; + final changePrefix = isPositive ? '+' : ''; + return Padding( - padding: const EdgeInsets.fromLTRB(20, 12, 20, 0), + padding: const EdgeInsets.fromLTRB(20, 16, 20, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -108,15 +207,15 @@ class _TradingDetailPageState extends State crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - '21.30', + '$_currencySymbol${_coupon.currentPrice.toStringAsFixed(2)}', style: AppTypography.priceLarge.copyWith( - color: AppColors.success, + color: changeColor, fontSize: 32, ), ), const SizedBox(width: 8), Text( - '≈ \$21.30', + '折扣 ${(_coupon.currentPrice / _coupon.faceValue * 10).toStringAsFixed(1)}折', style: AppTypography.bodySmall.copyWith( color: AppColors.textTertiary, ), @@ -126,11 +225,11 @@ class _TradingDetailPageState extends State padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: AppColors.success, + color: changeColor, borderRadius: AppSpacing.borderRadiusSm, ), child: Text( - '+2.05%', + '$changePrefix${_coupon.change24h.toStringAsFixed(2)}%', style: AppTypography.labelSmall.copyWith( color: Colors.white, fontWeight: FontWeight.w600, @@ -145,9 +244,9 @@ class _TradingDetailPageState extends State // 24h OHLC stats Row( children: [ - _buildOhlcStat('24h高', '21.75', AppColors.error), - _buildOhlcStat('24h低', '20.85', AppColors.success), - _buildOhlcStat('开盘', '20.87', AppColors.textPrimary), + _buildOhlcStat('24h高', '${_currencySymbol}21.75', AppColors.error), + _buildOhlcStat('24h低', '${_currencySymbol}20.85', AppColors.success), + _buildOhlcStat('开盘', '${_currencySymbol}20.87', AppColors.textPrimary), _buildOhlcStat('24h量', '342.5K', AppColors.textPrimary), ], ), @@ -199,7 +298,7 @@ class _TradingDetailPageState extends State top: 8, left: 12, child: Text( - 'SBUX/USDT · $_selectedPeriod', + '${_coupon.couponName} · $_selectedPeriod', style: AppTypography.caption.copyWith( color: AppColors.textTertiary, ), @@ -291,7 +390,7 @@ class _TradingDetailPageState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: AppTypography.caption.copyWith(color: color)), - Text('数量', style: AppTypography.caption), + Text('数量(张)', style: AppTypography.caption), ], ), const SizedBox(height: 6), @@ -322,7 +421,7 @@ class _TradingDetailPageState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - prices[i].toStringAsFixed(2), + '$_currencySymbol${prices[i].toStringAsFixed(2)}', style: AppTypography.caption.copyWith( color: color, fontWeight: FontWeight.w500, @@ -427,7 +526,7 @@ class _TradingDetailPageState extends State // Price input (hidden for market orders) if (_orderType == 'limit') ...[ - _buildInputField('价格', '21.30', 'USDT'), + _buildInputField('价格', '21.30', _currencySymbol), const SizedBox(height: 8), ], @@ -457,7 +556,9 @@ class _TradingDetailPageState extends State children: [ Text('可用', style: AppTypography.caption), Text( - _isBuy ? '1,234.56 USDT' : '3 张 SBUX', + _isBuy + ? '${_currencySymbol}1,234.56' + : '3 张 ${_coupon.couponName}', style: AppTypography.caption.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w500, @@ -483,7 +584,7 @@ class _TradingDetailPageState extends State ), ), child: Text( - _isBuy ? '买入 SBUX' : '卖出 SBUX', + _isBuy ? '买入 ${_coupon.couponName}' : '卖出 ${_coupon.couponName}', style: AppTypography.labelLarge.copyWith(color: Colors.white), ), ), @@ -605,17 +706,17 @@ class _TradingDetailPageState extends State const SizedBox(height: 12), // Mock orders - _buildOrderItem('买入', 'SBUX/USDT', '21.20', '5', '限价', - AppColors.success), + _buildOrderItem( + '买入', _coupon.couponName, '21.20', '5', '限价', AppColors.success), const Divider(height: 1), - _buildOrderItem('卖出', 'SBUX/USDT', '21.50', '2', '限价', - AppColors.error), + _buildOrderItem( + '卖出', _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) { return Padding( padding: const EdgeInsets.symmetric(vertical: 12), @@ -640,8 +741,8 @@ class _TradingDetailPageState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(pair, style: AppTypography.labelSmall), - Text('$type · $amount张 @ $price', + Text(couponName, style: AppTypography.labelSmall), + Text('$type · ${amount}张 @ $_currencySymbol$price', style: AppTypography.caption), ], ), @@ -720,6 +821,31 @@ class _TradingDetailPageState extends State } } +// ============================================================ +// 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 // ============================================================