feat: 全部前端项目完成国际化(i18n),支持中/英/日三语言

- miniapp (Taro/React): 11个页面/组件,~300翻译键
- admin-app (Flutter): 19个页面,475翻译键 (zh_CN/en_US/ja_JP)
- admin-web (Next.js): 25个视图+布局,2000+翻译键
- mobile (Flutter): 33+页面/组件,686翻译键 (zh_CN/zh_TW/en/ja)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-12 03:45:37 -08:00
parent 5bc1cbe4d8
commit 3cdb6a5eb9
105 changed files with 8740 additions and 3026 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'theme/app_colors.dart'; import 'theme/app_colors.dart';
import 'i18n/app_localizations.dart';
import '../features/dashboard/presentation/pages/issuer_dashboard_page.dart'; import '../features/dashboard/presentation/pages/issuer_dashboard_page.dart';
import '../features/coupon_management/presentation/pages/coupon_list_page.dart'; import '../features/coupon_management/presentation/pages/coupon_list_page.dart';
import '../features/redemption/presentation/pages/redemption_page.dart'; import '../features/redemption/presentation/pages/redemption_page.dart';
@ -39,31 +40,31 @@ class _IssuerMainShellState extends State<IssuerMainShell> {
onDestinationSelected: (index) { onDestinationSelected: (index) {
setState(() => _currentIndex = index); setState(() => _currentIndex = index);
}, },
destinations: const [ destinations: [
NavigationDestination( NavigationDestination(
icon: Icon(Icons.dashboard_outlined), icon: const Icon(Icons.dashboard_outlined),
selectedIcon: Icon(Icons.dashboard_rounded), selectedIcon: const Icon(Icons.dashboard_rounded),
label: '数据概览', label: context.t('tab_dashboard'),
), ),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.confirmation_number_outlined), icon: const Icon(Icons.confirmation_number_outlined),
selectedIcon: Icon(Icons.confirmation_number_rounded), selectedIcon: const Icon(Icons.confirmation_number_rounded),
label: '券管理', label: context.t('tab_coupons'),
), ),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.qr_code_scanner_outlined), icon: const Icon(Icons.qr_code_scanner_outlined),
selectedIcon: Icon(Icons.qr_code_scanner_rounded), selectedIcon: const Icon(Icons.qr_code_scanner_rounded),
label: '核销', label: context.t('tab_redemption'),
), ),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.account_balance_wallet_outlined), icon: const Icon(Icons.account_balance_wallet_outlined),
selectedIcon: Icon(Icons.account_balance_wallet_rounded), selectedIcon: const Icon(Icons.account_balance_wallet_rounded),
label: '财务', label: context.t('tab_finance'),
), ),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.settings_outlined), icon: const Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings_rounded), selectedIcon: const Icon(Icons.settings_rounded),
label: '我的', label: context.t('tab_mine'),
), ),
], ],
), ),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
/// AI Agent对话页面 /// AI Agent对话页面
/// ///
@ -15,30 +16,33 @@ class _AiAgentPageState extends State<AiAgentPage> {
final _messageController = TextEditingController(); final _messageController = TextEditingController();
final _scrollController = ScrollController(); final _scrollController = ScrollController();
final List<_ChatMessage> _messages = [ final List<_ChatMessage> _messages = [];
_ChatMessage( bool _initialized = false;
isAi: true,
text: '您好!我是 Genex AI 助手,可以帮您分析销售数据、优化定价策略、提升信用评级。有什么可以帮您的吗?',
),
];
final _quickActions = [ final _quickActionKeys = [
'分析本月销售数据', 'ai_agent_action_sales',
'推荐最优发券时间', 'ai_agent_action_timing',
'如何提升信用评级?', 'ai_agent_action_credit',
'额度使用情况分析', 'ai_agent_action_quota',
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!_initialized) {
_messages.add(_ChatMessage(
isAi: true,
text: context.t('ai_agent_welcome'),
));
_initialized = true;
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Row( title: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20), const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20),
SizedBox(width: 8), const SizedBox(width: 8),
Text('AI 助手'), Text(context.t('ai_agent_title')),
], ],
), ),
), ),
@ -60,7 +64,8 @@ class _AiAgentPageState extends State<AiAgentPage> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row( child: Row(
children: _quickActions.map((action) { children: _quickActionKeys.map((key) {
final action = context.t(key);
return Padding( return Padding(
padding: const EdgeInsets.only(right: 8), padding: const EdgeInsets.only(right: 8),
child: ActionChip( child: ActionChip(
@ -87,7 +92,7 @@ class _AiAgentPageState extends State<AiAgentPage> {
child: TextField( child: TextField(
controller: _messageController, controller: _messageController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '输入问题...', hintText: context.t('ai_agent_input_hint'),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
borderSide: const BorderSide(color: AppColors.borderLight), borderSide: const BorderSide(color: AppColors.borderLight),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/router.dart'; import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -42,14 +43,14 @@ class _IssuerLoginPageState extends State<IssuerLoginPage> {
const SizedBox(height: 24), const SizedBox(height: 24),
// Title // Title
const Text( Text(
'Genex 发行方控制台', context.t('login_title'),
style: TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: AppColors.textPrimary), style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: AppColors.textPrimary),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( Text(
'登录您的企业账号管理券发行', context.t('login_subtitle'),
style: TextStyle(fontSize: 15, color: AppColors.textSecondary), style: const TextStyle(fontSize: 15, color: AppColors.textSecondary),
), ),
const SizedBox(height: 40), const SizedBox(height: 40),
@ -57,10 +58,10 @@ class _IssuerLoginPageState extends State<IssuerLoginPage> {
TextField( TextField(
controller: _phoneController, controller: _phoneController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: '手机号', labelText: context.t('login_phone'),
prefixIcon: Icon(Icons.phone_outlined), prefixIcon: const Icon(Icons.phone_outlined),
hintText: '请输入企业管理员手机号', hintText: context.t('login_phone_hint'),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -72,9 +73,9 @@ class _IssuerLoginPageState extends State<IssuerLoginPage> {
child: TextField( child: TextField(
controller: _codeController, controller: _codeController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: '验证码', labelText: context.t('login_code'),
prefixIcon: Icon(Icons.lock_outline_rounded), prefixIcon: const Icon(Icons.lock_outline_rounded),
), ),
), ),
), ),
@ -85,7 +86,7 @@ class _IssuerLoginPageState extends State<IssuerLoginPage> {
onPressed: () { onPressed: () {
// TODO: Send verification code to phone number // TODO: Send verification code to phone number
}, },
child: const Text('获取验证码'), child: Text(context.t('login_get_code')),
), ),
), ),
], ],
@ -103,11 +104,11 @@ class _IssuerLoginPageState extends State<IssuerLoginPage> {
Expanded( Expanded(
child: Text.rich( child: Text.rich(
TextSpan( TextSpan(
text: '我已阅读并同意', text: context.t('login_agree_prefix'),
style: const TextStyle(fontSize: 13, color: AppColors.textSecondary), style: const TextStyle(fontSize: 13, color: AppColors.textSecondary),
children: [ children: [
TextSpan( TextSpan(
text: '《发行方服务协议》', text: context.t('login_agreement'),
style: const TextStyle(color: AppColors.primary), style: const TextStyle(color: AppColors.primary),
), ),
], ],
@ -126,7 +127,7 @@ class _IssuerLoginPageState extends State<IssuerLoginPage> {
onPressed: _agreedToTerms onPressed: _agreedToTerms
? () => Navigator.pushReplacementNamed(context, AppRouter.main) ? () => Navigator.pushReplacementNamed(context, AppRouter.main)
: null, : null,
child: const Text('登录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), child: Text(context.t('login_button'), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -135,7 +136,7 @@ class _IssuerLoginPageState extends State<IssuerLoginPage> {
Center( Center(
child: TextButton( child: TextButton(
onPressed: () => Navigator.pushNamed(context, AppRouter.onboarding), onPressed: () => Navigator.pushNamed(context, AppRouter.onboarding),
child: const Text('还没有账号?申请入驻'), child: Text(context.t('login_register')),
), ),
), ),
], ],

View File

@ -2,6 +2,7 @@ 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 '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -58,22 +59,22 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('批量操作'), title: Text(context.t('batch_title')),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.history_rounded), icon: const Icon(Icons.history_rounded),
onPressed: () { onPressed: () {
// TODO: Navigate to full operation history page // TODO: Navigate to full operation history page
}, },
tooltip: '操作历史', tooltip: context.t('batch_history_tooltip'),
), ),
], ],
bottom: TabBar( bottom: TabBar(
controller: _tabController, controller: _tabController,
tabs: const [ tabs: [
Tab(text: '批量发行'), Tab(text: context.t('batch_tab_issue')),
Tab(text: '批量召回'), Tab(text: context.t('batch_tab_recall')),
Tab(text: '批量调价'), Tab(text: context.t('batch_tab_price')),
], ],
), ),
), ),
@ -122,7 +123,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'批量操作进行中...', context.t('batch_progress_label'),
style: AppTypography.labelMedium.copyWith(color: AppColors.primary), style: AppTypography.labelMedium.copyWith(color: AppColors.primary),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -159,19 +160,19 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Template Selector // Template Selector
_buildSectionTitle('选择券模板'), _buildSectionTitle(context.t('batch_select_template')),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildTemplateSelector(), _buildTemplateSelector(),
const SizedBox(height: 24), const SizedBox(height: 24),
// Quantity Input // Quantity Input
_buildSectionTitle('发行数量'), _buildSectionTitle(context.t('batch_issue_quantity')),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildQuantitySelector(), _buildQuantitySelector(),
const SizedBox(height: 24), const SizedBox(height: 24),
// Date Range Picker // Date Range Picker
_buildSectionTitle('有效期范围'), _buildSectionTitle(context.t('batch_validity_range')),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildDateRangePicker(), _buildDateRangePicker(),
const SizedBox(height: 24), const SizedBox(height: 24),
@ -185,7 +186,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: _selectedTemplate != null ? _executeBatchIssue : null, onPressed: _selectedTemplate != null ? _executeBatchIssue : null,
child: const Text('确认批量发行'), child: Text(context.t('batch_confirm_issue')),
), ),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
@ -312,7 +313,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
autofocus: true, autofocus: true,
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: '输入自定义数量', hintText: context.t('batch_custom_quantity_hint'),
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
@ -327,7 +328,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
) )
: Center( : Center(
child: Text( child: Text(
'自定义数量', context.t('batch_custom_quantity'),
style: AppTypography.labelMedium.copyWith( style: AppTypography.labelMedium.copyWith(
color: AppColors.textSecondary, color: AppColors.textSecondary,
), ),
@ -376,7 +377,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
child: Text( child: Text(
_dateRange != null _dateRange != null
? '${_formatDate(_dateRange!.start)} ~ ${_formatDate(_dateRange!.end)}' ? '${_formatDate(_dateRange!.start)} ~ ${_formatDate(_dateRange!.end)}'
: '点击选择有效期范围', : context.t('batch_date_range_hint'),
style: AppTypography.bodyMedium.copyWith( style: AppTypography.bodyMedium.copyWith(
color: _dateRange != null ? AppColors.textPrimary : AppColors.textTertiary, color: _dateRange != null ? AppColors.textPrimary : AppColors.textTertiary,
), ),
@ -403,20 +404,20 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
children: [ children: [
const Icon(Icons.preview_rounded, color: AppColors.primary, size: 18), const Icon(Icons.preview_rounded, color: AppColors.primary, size: 18),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('发行预览', style: AppTypography.labelMedium.copyWith(color: AppColors.primary)), Text(context.t('batch_preview'), style: AppTypography.labelMedium.copyWith(color: AppColors.primary)),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildSummaryRow('券模板', _selectedTemplate ?? '未选择'), _buildSummaryRow(context.t('batch_preview_template'), _selectedTemplate ?? context.t('batch_not_selected')),
_buildSummaryRow('发行数量', '$_issueQuantity'), _buildSummaryRow(context.t('batch_preview_quantity'), '$_issueQuantity ${context.t('batch_unit_sheets')}'),
_buildSummaryRow( _buildSummaryRow(
'有效期', context.t('batch_preview_validity'),
_dateRange != null _dateRange != null
? '${_formatDate(_dateRange!.start)} ~ ${_formatDate(_dateRange!.end)}' ? '${_formatDate(_dateRange!.start)} ~ ${_formatDate(_dateRange!.end)}'
: '未设置', : context.t('batch_not_set'),
), ),
_buildSummaryRow( _buildSummaryRow(
'预估总面值', context.t('batch_preview_total_value'),
_selectedTemplate != null ? '\$${(_issueQuantity * 25).toStringAsFixed(0)}' : '--', _selectedTemplate != null ? '\$${(_issueQuantity * 25).toStringAsFixed(0)}' : '--',
), ),
], ],
@ -444,19 +445,19 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('确认批量发行'), title: Text(context.t('batch_confirm_issue')),
content: Text('将发行 $_issueQuantity$_selectedTemplate,确认执行?'), content: Text(context.t('batch_confirm_issue_desc').replaceAll('{count}', '$_issueQuantity').replaceAll('{template}', _selectedTemplate ?? '')),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Text('取消'), child: Text(context.t('cancel')),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.pop(ctx); Navigator.pop(ctx);
_startOperation(); _startOperation();
}, },
child: const Text('确认'), child: Text(context.t('confirm')),
), ),
], ],
), ),
@ -477,7 +478,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Filter by category // Filter by category
_buildSectionTitle('按类别筛选'), _buildSectionTitle(context.t('batch_filter_category')),
const SizedBox(height: 12), const SizedBox(height: 12),
SingleChildScrollView( SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
@ -500,7 +501,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
const SizedBox(height: 16), const SizedBox(height: 16),
// Filter by status // Filter by status
_buildSectionTitle('按状态筛选'), _buildSectionTitle(context.t('batch_filter_status')),
const SizedBox(height: 12), const SizedBox(height: 12),
SingleChildScrollView( SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
@ -526,7 +527,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('符合条件的券 (${_mockRecallCoupons.length})', style: AppTypography.labelMedium), Text('${context.t('batch_matching_coupons')} (${_mockRecallCoupons.length})', style: AppTypography.labelMedium),
Row( Row(
children: [ children: [
TextButton( TextButton(
@ -536,14 +537,14 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
List.generate(_mockRecallCoupons.length, (i) => i), List.generate(_mockRecallCoupons.length, (i) => i),
); );
}), }),
child: const Text('全选'), child: Text(context.t('batch_select_all')),
), ),
TextButton( TextButton(
onPressed: () => setState(() { onPressed: () => setState(() {
_selectAll = false; _selectAll = false;
_selectedRecallItems.clear(); _selectedRecallItems.clear();
}), }),
child: const Text('取消全选'), child: Text(context.t('batch_deselect_all')),
), ),
], ],
), ),
@ -586,7 +587,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
Text(coupon.name, style: AppTypography.labelMedium), Text(coupon.name, style: AppTypography.labelMedium),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
'${coupon.category} · 剩余 ${coupon.remaining}', '${coupon.category} · ${context.t('batch_remaining')} ${coupon.remaining}${context.t('batch_unit_sheets')}',
style: AppTypography.bodySmall, style: AppTypography.bodySmall,
), ),
], ],
@ -600,13 +601,13 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
const SizedBox(height: 20), const SizedBox(height: 20),
// Reason input // Reason input
_buildSectionTitle('召回原因'), _buildSectionTitle(context.t('batch_recall_reason')),
const SizedBox(height: 12), const SizedBox(height: 12),
TextField( TextField(
controller: _recallReasonController, controller: _recallReasonController,
maxLines: 3, maxLines: 3,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '请输入批量召回原因(必填)', hintText: context.t('batch_recall_reason_hint'),
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@ -623,7 +624,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
backgroundColor: AppColors.error, backgroundColor: AppColors.error,
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
child: Text('确认召回 (${_selectedRecallItems.length})'), child: Text('${context.t('batch_confirm_recall')} (${_selectedRecallItems.length} ${context.t('batch_unit_sheets')})'),
), ),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
@ -643,16 +644,16 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
children: [ children: [
Icon(Icons.warning_rounded, color: AppColors.error, size: 22), Icon(Icons.warning_rounded, color: AppColors.error, size: 22),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text('确认批量召回'), Text(context.t('batch_confirm_recall_title')),
], ],
), ),
content: Text( content: Text(
'将召回 ${_selectedRecallItems.length} 张券,此操作不可撤销。\n\n原因:${_recallReasonController.text}', '${context.t('batch_confirm_recall_desc').replaceAll('{count}', '${_selectedRecallItems.length}')}\n\n${context.t('batch_recall_reason_label')}${_recallReasonController.text}',
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Text('取消'), child: Text(context.t('cancel')),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
@ -663,7 +664,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
backgroundColor: AppColors.error, backgroundColor: AppColors.error,
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
child: const Text('确认召回'), child: Text(context.t('batch_confirm_recall')),
), ),
], ],
), ),
@ -681,7 +682,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Percentage adjustment slider // Percentage adjustment slider
_buildSectionTitle('价格调整比例'), _buildSectionTitle(context.t('batch_price_adjust_ratio')),
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
@ -734,7 +735,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
const SizedBox(height: 24), const SizedBox(height: 24),
// Affected coupons preview // Affected coupons preview
_buildSectionTitle('受影响的券'), _buildSectionTitle(context.t('batch_affected_coupons')),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -777,10 +778,10 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
Expanded( Expanded(
child: Text( child: Text(
_priceAdjustment < 0 _priceAdjustment < 0
? '降价 ${_priceAdjustment.abs().toStringAsFixed(0)}% 预计可提升销量 ${(_priceAdjustment.abs() * 1.5).toStringAsFixed(0)}%' ? context.t('batch_price_decrease_hint').replaceAll('{pct}', _priceAdjustment.abs().toStringAsFixed(0)).replaceAll('{impact}', (_priceAdjustment.abs() * 1.5).toStringAsFixed(0))
: _priceAdjustment > 0 : _priceAdjustment > 0
? '涨价 ${_priceAdjustment.toStringAsFixed(0)}% 预计利润提升 ${(_priceAdjustment * 0.8).toStringAsFixed(0)}%' ? context.t('batch_price_increase_hint').replaceAll('{pct}', _priceAdjustment.toStringAsFixed(0)).replaceAll('{impact}', (_priceAdjustment * 0.8).toStringAsFixed(0))
: '当前价格不变', : context.t('batch_price_no_change'),
style: AppTypography.bodySmall.copyWith( style: AppTypography.bodySmall.copyWith(
color: _priceAdjustment < 0 ? AppColors.warning : AppColors.success, color: _priceAdjustment < 0 ? AppColors.warning : AppColors.success,
), ),
@ -796,7 +797,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: _priceAdjustment != 0 ? _executeBatchPriceAdjust : null, onPressed: _priceAdjustment != 0 ? _executeBatchPriceAdjust : null,
child: const Text('确认批量调价'), child: Text(context.t('batch_confirm_price')),
), ),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
@ -819,7 +820,7 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
children: [ children: [
Text(name, style: AppTypography.labelMedium), Text(name, style: AppTypography.labelMedium),
const SizedBox(height: 2), const SizedBox(height: 2),
Text('当前价: \$${currentPrice.toStringAsFixed(2)}', style: AppTypography.bodySmall), Text('${context.t('batch_current_price')}: \$${currentPrice.toStringAsFixed(2)}', style: AppTypography.bodySmall),
], ],
), ),
), ),
@ -856,21 +857,21 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('确认批量调价'), title: Text(context.t('batch_confirm_price')),
content: Text( content: Text(
'将对 4 种券批量调价 ${_priceAdjustment > 0 ? '+' : ''}${_priceAdjustment.toStringAsFixed(0)}%,确认执行?', '将对 4 种券批量调价 ${_priceAdjustment > 0 ? '+' : ''}${_priceAdjustment.toStringAsFixed(0)}%,确认执行?',
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Text('取消'), child: Text(context.t('cancel')),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.pop(ctx); Navigator.pop(ctx);
_startOperation(); _startOperation();
}, },
child: const Text('确认'), child: Text(context.t('confirm')),
), ),
], ],
), ),
@ -888,12 +889,12 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('操作历史', style: AppTypography.h3), Text(context.t('batch_operation_history'), style: AppTypography.h3),
TextButton( TextButton(
onPressed: () { onPressed: () {
// TODO: Navigate to full operation history page // TODO: Navigate to full operation history page
}, },
child: const Text('查看全部'), child: Text(context.t('view_all')),
), ),
], ],
), ),
@ -1011,8 +1012,8 @@ class _BatchOperationsPageState extends State<BatchOperationsPage>
}); });
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( SnackBar(
content: Text('批量操作完成'), content: Text(context.t('batch_operation_complete')),
backgroundColor: AppColors.success, backgroundColor: AppColors.success,
), ),
); );

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -12,7 +13,7 @@ class IssuerCouponDetailPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('券详情'), title: Text(context.t('coupon_detail_title')),
actions: [ actions: [
PopupMenuButton<String>( PopupMenuButton<String>(
onSelected: (value) { onSelected: (value) {
@ -20,10 +21,10 @@ class IssuerCouponDetailPage extends StatelessWidget {
if (value == 'delist') _showDelistDialog(context); if (value == 'delist') _showDelistDialog(context);
}, },
itemBuilder: (ctx) => [ itemBuilder: (ctx) => [
const PopupMenuItem(value: 'edit', child: Text('编辑信息')), PopupMenuItem(value: 'edit', child: Text(context.t('coupon_detail_edit'))),
const PopupMenuItem(value: 'reissue', child: Text('增发')), PopupMenuItem(value: 'reissue', child: Text(context.t('coupon_detail_reissue'))),
const PopupMenuItem(value: 'delist', child: Text('下架')), PopupMenuItem(value: 'delist', child: Text(context.t('coupon_detail_delist'))),
const PopupMenuItem(value: 'recall', child: Text('召回未售出')), PopupMenuItem(value: 'recall', child: Text(context.t('coupon_detail_recall_unsold'))),
], ],
), ),
], ],
@ -34,30 +35,30 @@ class IssuerCouponDetailPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Header Card // Header Card
_buildHeaderCard(), _buildHeaderCard(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Sales Data // Sales Data
_buildSalesDataCard(), _buildSalesDataCard(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Secondary Market Analysis // Secondary Market Analysis
_buildSecondaryMarketCard(), _buildSecondaryMarketCard(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Financing Effect // Financing Effect
_buildFinancingEffectCard(), _buildFinancingEffectCard(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Redemption Timeline // Redemption Timeline
_buildRedemptionTimeline(), _buildRedemptionTimeline(context),
], ],
), ),
), ),
); );
} }
Widget _buildHeaderCard() { Widget _buildHeaderCard(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -81,7 +82,7 @@ class IssuerCouponDetailPage extends StatelessWidget {
color: Colors.white.withValues(alpha: 0.2), color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(999), borderRadius: BorderRadius.circular(999),
), ),
child: const Text('在售中', style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600)), child: Text(context.t('coupon_detail_status_on_sale'), style: const TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600)),
), ),
], ],
), ),
@ -94,10 +95,10 @@ class IssuerCouponDetailPage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildHeaderStat('发行量', '5,000'), _buildHeaderStat(context.t('coupon_stat_issued'), '5,000'),
_buildHeaderStat('已售', '4,200'), _buildHeaderStat(context.t('coupon_stat_sold'), '4,200'),
_buildHeaderStat('已核销', '3,300'), _buildHeaderStat(context.t('coupon_stat_redeemed'), '3,300'),
_buildHeaderStat('核销率', '78.5%'), _buildHeaderStat(context.t('coupon_stat_rate'), '78.5%'),
], ],
), ),
], ],
@ -115,7 +116,7 @@ class IssuerCouponDetailPage extends StatelessWidget {
); );
} }
Widget _buildSalesDataCard() { Widget _buildSalesDataCard(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -126,13 +127,13 @@ class IssuerCouponDetailPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('销售数据', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('coupon_detail_sales_data'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildDataRow('销售收入', '\$89,250'), _buildDataRow(context.t('coupon_detail_sales_income'), '\$89,250'),
_buildDataRow('Breakage收入过期券', '\$3,400'), _buildDataRow(context.t('coupon_detail_breakage_income'), '\$3,400'),
_buildDataRow('平台手续费', '-\$1,070'), _buildDataRow(context.t('coupon_detail_platform_fee'), '-\$1,070'),
const Divider(height: 24), const Divider(height: 24),
_buildDataRow('净收入', '\$91,580', bold: true), _buildDataRow(context.t('coupon_detail_net_income'), '\$91,580', bold: true),
const SizedBox(height: 16), const SizedBox(height: 16),
// Chart placeholder // Chart placeholder
Container( Container(
@ -141,14 +142,14 @@ class IssuerCouponDetailPage extends StatelessWidget {
color: AppColors.gray50, color: AppColors.gray50,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Center(child: Text('日销量趋势', style: TextStyle(color: AppColors.textTertiary))), child: Center(child: Text(context.t('coupon_detail_daily_trend'), style: const TextStyle(color: AppColors.textTertiary))),
), ),
], ],
), ),
); );
} }
Widget _buildSecondaryMarketCard() { Widget _buildSecondaryMarketCard(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -159,13 +160,13 @@ class IssuerCouponDetailPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('二级市场分析', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('coupon_detail_secondary_market'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildDataRow('挂单数', '128'), _buildDataRow(context.t('coupon_detail_listing_count'), '128'),
_buildDataRow('平均转售价', '\$22.80'), _buildDataRow(context.t('coupon_detail_avg_resale_price'), '\$22.80'),
_buildDataRow('平均折扣率', '91.2%'), _buildDataRow(context.t('coupon_detail_avg_discount_rate'), '91.2%'),
_buildDataRow('转售成交量', '856'), _buildDataRow(context.t('coupon_detail_resale_volume'), '856'),
_buildDataRow('转售成交额', '\$19,517'), _buildDataRow(context.t('coupon_detail_resale_amount'), '\$19,517'),
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
height: 100, height: 100,
@ -173,14 +174,14 @@ class IssuerCouponDetailPage extends StatelessWidget {
color: AppColors.gray50, color: AppColors.gray50,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Center(child: Text('价格走势K线', style: TextStyle(color: AppColors.textTertiary))), child: Center(child: Text(context.t('coupon_detail_price_chart'), style: const TextStyle(color: AppColors.textTertiary))),
), ),
], ],
), ),
); );
} }
Widget _buildFinancingEffectCard() { Widget _buildFinancingEffectCard(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -191,18 +192,18 @@ class IssuerCouponDetailPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('融资效果', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('coupon_detail_financing_effect'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildDataRow('现金提前回笼', '\$89,250'), _buildDataRow(context.t('coupon_detail_cash_advance'), '\$89,250'),
_buildDataRow('平均提前回笼天数', '45 天'), _buildDataRow(context.t('coupon_detail_avg_advance_days'), '45 天'),
_buildDataRow('融资成本', '\$4,463'), _buildDataRow(context.t('coupon_detail_financing_cost'), '\$4,463'),
_buildDataRow('等效年利率', '3.6%'), _buildDataRow(context.t('coupon_detail_equiv_annual_rate'), '3.6%'),
], ],
), ),
); );
} }
Widget _buildRedemptionTimeline() { Widget _buildRedemptionTimeline(BuildContext context) {
final events = [ final events = [
('核销 5 张 · 门店A', '10分钟前', AppColors.success), ('核销 5 张 · 门店A', '10分钟前', AppColors.success),
('核销 2 张 · 门店B', '25分钟前', AppColors.success), ('核销 2 张 · 门店B', '25分钟前', AppColors.success),
@ -220,7 +221,7 @@ class IssuerCouponDetailPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('最近核销记录', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('coupon_detail_recent_redemptions'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 12), const SizedBox(height: 12),
...events.map((e) { ...events.map((e) {
final (desc, time, color) = e; final (desc, time, color) = e;
@ -258,11 +259,11 @@ class IssuerCouponDetailPage extends StatelessWidget {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('召回未售出券'), title: Text(context.t('coupon_detail_recall_title')),
content: const Text('确认召回所有未售出的券?此操作不可逆。'), content: Text(context.t('coupon_detail_recall_desc')),
actions: [ actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消')), TextButton(onPressed: () => Navigator.pop(ctx), child: Text(context.t('cancel'))),
ElevatedButton(onPressed: () => Navigator.pop(ctx), child: const Text('确认召回')), ElevatedButton(onPressed: () => Navigator.pop(ctx), child: Text(context.t('coupon_detail_confirm_recall'))),
], ],
), ),
); );
@ -272,21 +273,21 @@ class IssuerCouponDetailPage extends StatelessWidget {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('紧急下架'), title: Text(context.t('coupon_detail_delist_title')),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text('确认下架此券?下架后消费者将无法购买。'), Text(context.t('coupon_detail_delist_desc')),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField(decoration: const InputDecoration(labelText: '下架原因')), TextField(decoration: InputDecoration(labelText: context.t('coupon_detail_delist_reason'))),
], ],
), ),
actions: [ actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消')), TextButton(onPressed: () => Navigator.pop(ctx), child: Text(context.t('cancel'))),
ElevatedButton( ElevatedButton(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.error), style: ElevatedButton.styleFrom(backgroundColor: AppColors.error),
child: const Text('确认下架'), child: Text(context.t('coupon_detail_confirm_delist')),
), ),
], ],
), ),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/router.dart'; import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
/// - /// -
/// ///
@ -13,7 +14,7 @@ class CouponListPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('券管理'), title: Text(context.t('tab_coupons')),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.search_rounded), icon: const Icon(Icons.search_rounded),
@ -35,7 +36,7 @@ class CouponListPage extends StatelessWidget {
_buildAiSuggestion(context), _buildAiSuggestion(context),
// Filter Chips // Filter Chips
_buildFilterChips(), _buildFilterChips(context),
// Coupon List // Coupon List
Expanded( Expanded(
@ -55,7 +56,7 @@ class CouponListPage extends StatelessWidget {
backgroundColor: AppColors.primary, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: Colors.white,
icon: const Icon(Icons.add_rounded), icon: const Icon(Icons.add_rounded),
label: const Text('发券'), label: Text(context.t('coupon_list_fab')),
), ),
); );
} }
@ -72,10 +73,10 @@ class CouponListPage extends StatelessWidget {
children: [ children: [
const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 18), const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 18),
const SizedBox(width: 8), const SizedBox(width: 8),
const Expanded( Expanded(
child: Text( child: Text(
'建议周末发行餐饮券销量通常提升30%', context.t('coupon_list_ai_suggestion'),
style: TextStyle(fontSize: 12, color: AppColors.primary), style: const TextStyle(fontSize: 12, color: AppColors.primary),
), ),
), ),
GestureDetector( GestureDetector(
@ -89,14 +90,20 @@ class CouponListPage extends StatelessWidget {
); );
} }
Widget _buildFilterChips() { Widget _buildFilterChips(BuildContext context) {
final filters = ['全部', '在售中', '已售罄', '待审核', '已下架']; final filters = [
context.t('all'),
context.t('coupon_filter_on_sale'),
context.t('coupon_filter_sold_out'),
context.t('coupon_filter_pending'),
context.t('coupon_filter_delisted'),
];
return SingleChildScrollView( return SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
child: Row( child: Row(
children: filters.map((f) { children: filters.map((f) {
final isSelected = f == '全部'; final isSelected = f == context.t('all');
return Padding( return Padding(
padding: const EdgeInsets.only(right: 8), padding: const EdgeInsets.only(right: 8),
child: FilterChip( child: FilterChip(
@ -150,7 +157,7 @@ class CouponListPage extends StatelessWidget {
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
'${coupon.template} · 面值 \$${coupon.faceValue}', '${coupon.template} · ${context.t('coupon_face_value')} \$${coupon.faceValue}',
style: const TextStyle(fontSize: 12, color: AppColors.textSecondary), style: const TextStyle(fontSize: 12, color: AppColors.textSecondary),
), ),
], ],
@ -163,10 +170,10 @@ class CouponListPage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildMiniStat('发行量', '${coupon.issued}'), _buildMiniStat(context.t('coupon_stat_issued'), '${coupon.issued}'),
_buildMiniStat('已售', '${coupon.sold}'), _buildMiniStat(context.t('coupon_stat_sold'), '${coupon.sold}'),
_buildMiniStat('已核销', '${coupon.redeemed}'), _buildMiniStat(context.t('coupon_stat_redeemed'), '${coupon.redeemed}'),
_buildMiniStat('核销率', '${coupon.redemptionRate}%'), _buildMiniStat(context.t('coupon_stat_rate'), '${coupon.redemptionRate}%'),
], ],
), ),
], ],

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -29,13 +30,13 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('发行新券'), title: Text(context.t('create_coupon_title')),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
}, },
child: const Text('存为草稿'), child: Text(context.t('create_coupon_save_draft')),
), ),
], ],
), ),
@ -60,7 +61,12 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
} }
Widget _buildStepBar() { Widget _buildStepBar() {
final steps = ['选择模板', '基本信息', '规则设置', '预览确认']; final steps = [
context.t('create_coupon_step_template'),
context.t('create_coupon_step_info'),
context.t('create_coupon_step_rules'),
context.t('create_coupon_step_preview'),
];
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Row( child: Row(
@ -107,18 +113,18 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
Widget _buildTemplateStep() { Widget _buildTemplateStep() {
final templates = [ final templates = [
('折扣券', '按比例打折', Icons.percent_rounded, AppColors.error), (context.t('create_coupon_tpl_discount'), context.t('create_coupon_tpl_discount_desc'), Icons.percent_rounded, AppColors.error),
('代金券', '抵扣固定金额', Icons.money_rounded, AppColors.primary), (context.t('create_coupon_tpl_voucher'), context.t('create_coupon_tpl_voucher_desc'), Icons.money_rounded, AppColors.primary),
('礼品卡', '可充值消费', Icons.card_giftcard_rounded, AppColors.info), (context.t('create_coupon_tpl_gift'), context.t('create_coupon_tpl_gift_desc'), Icons.card_giftcard_rounded, AppColors.info),
('储值券', '预存金额消费', Icons.account_balance_wallet_rounded, AppColors.success), (context.t('create_coupon_tpl_stored'), context.t('create_coupon_tpl_stored_desc'), Icons.account_balance_wallet_rounded, AppColors.success),
]; ];
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('选择券模板', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('create_coupon_select_template'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('选择适合您业务场景的券类型', style: TextStyle(color: AppColors.textSecondary)), Text(context.t('create_coupon_select_template_desc'), style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 24), const SizedBox(height: 24),
...templates.map((t) { ...templates.map((t) {
final (name, desc, icon, color) = t; final (name, desc, icon, color) = t;
@ -172,23 +178,23 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('基本信息', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('create_coupon_step_info'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 24), const SizedBox(height: 24),
TextField( TextField(
controller: _nameController, controller: _nameController,
decoration: const InputDecoration(labelText: '券名称', hintText: '¥25 星巴克礼品卡'), decoration: InputDecoration(labelText: context.t('create_coupon_name'), hintText: context.t('create_coupon_name_hint')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _faceValueController, controller: _faceValueController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: '面值 (\$)', hintText: '输入面值金额'), decoration: InputDecoration(labelText: context.t('create_coupon_face_value'), hintText: context.t('create_coupon_face_value_hint')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _issuePriceController, controller: _issuePriceController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: '发行价 (\$)', hintText: '通常低于面值'), decoration: InputDecoration(labelText: context.t('create_coupon_issue_price'), hintText: context.t('create_coupon_issue_price_hint')),
), ),
// AI Price Suggestion // AI Price Suggestion
@ -199,14 +205,14 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
color: AppColors.primarySurface, color: AppColors.primarySurface,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: const Row( child: Row(
children: [ children: [
Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 16), const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 16),
SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'AI建议面值¥25的礼品卡最优发行价为¥21.258.5折),可最大化销量', context.t('create_coupon_ai_price_suggestion'),
style: TextStyle(fontSize: 12, color: AppColors.primary), style: const TextStyle(fontSize: 12, color: AppColors.primary),
), ),
), ),
], ],
@ -217,18 +223,18 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
TextField( TextField(
controller: _quantityController, controller: _quantityController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: '发行数量', hintText: '本次发行总量'), decoration: InputDecoration(labelText: context.t('create_coupon_quantity'), hintText: context.t('create_coupon_quantity_hint')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
InputDatePickerFormField( InputDatePickerFormField(
firstDate: DateTime.now(), firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)), lastDate: DateTime.now().add(const Duration(days: 365)),
fieldLabelText: '有效期截止日最长12个月', fieldLabelText: context.t('create_coupon_expiry'),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
maxLines: 3, maxLines: 3,
decoration: const InputDecoration(labelText: '券描述(可选)', hintText: '详细描述使用规则'), decoration: InputDecoration(labelText: context.t('create_coupon_description'), hintText: context.t('create_coupon_description_hint')),
), ),
], ],
); );
@ -238,13 +244,13 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('规则设置', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('create_coupon_step_rules'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 24), const SizedBox(height: 24),
// Transfer settings // Transfer settings
SwitchListTile( SwitchListTile(
title: const Text('允许转让'), title: Text(context.t('create_coupon_transferable')),
subtitle: const Text('消费者可在二级市场转售此券'), subtitle: Text(context.t('create_coupon_transferable_desc')),
value: _transferable, value: _transferable,
onChanged: (v) => setState(() => _transferable = v), onChanged: (v) => setState(() => _transferable = v),
activeColor: AppColors.primary, activeColor: AppColors.primary,
@ -254,7 +260,7 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
children: [ children: [
const Text('最大转售次数', style: TextStyle(fontSize: 14)), Text(context.t('create_coupon_max_resale'), style: const TextStyle(fontSize: 14)),
const Spacer(), const Spacer(),
IconButton( IconButton(
icon: const Icon(Icons.remove_circle_outline), icon: const Icon(Icons.remove_circle_outline),
@ -271,11 +277,11 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
const Divider(height: 32), const Divider(height: 32),
// Refund Policy // Refund Policy
const Text('退款策略', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), Text(context.t('create_coupon_refund_policy'), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
children: [ children: [
const Text('退款窗口(天)', style: TextStyle(fontSize: 14)), Text(context.t('create_coupon_refund_window'), style: const TextStyle(fontSize: 14)),
const Spacer(), const Spacer(),
IconButton( IconButton(
icon: const Icon(Icons.remove_circle_outline), icon: const Icon(Icons.remove_circle_outline),
@ -289,8 +295,8 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
], ],
), ),
SwitchListTile( SwitchListTile(
title: const Text('允许自动退款'), title: Text(context.t('create_coupon_auto_refund')),
subtitle: const Text('窗口期内用户可直接退款无需审核'), subtitle: Text(context.t('create_coupon_auto_refund_desc')),
value: _autoRefund, value: _autoRefund,
onChanged: (v) => setState(() => _autoRefund = v), onChanged: (v) => setState(() => _autoRefund = v),
activeColor: AppColors.primary, activeColor: AppColors.primary,
@ -300,8 +306,8 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
// Usage Rules // Usage Rules
SwitchListTile( SwitchListTile(
title: const Text('可叠加使用'), title: Text(context.t('create_coupon_stackable')),
subtitle: const Text('同一订单可使用多张此券'), subtitle: Text(context.t('create_coupon_stackable_desc')),
value: false, value: false,
onChanged: (_) { onChanged: (_) {
// TODO: Toggle stackable usage setting // TODO: Toggle stackable usage setting
@ -310,11 +316,11 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
), ),
TextField( TextField(
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: '最低消费金额 (\$,可选)'), decoration: InputDecoration(labelText: context.t('create_coupon_min_purchase')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
decoration: const InputDecoration(labelText: '限定门店(可选)', hintText: '留空则全部门店可用'), decoration: InputDecoration(labelText: context.t('create_coupon_store_limit'), hintText: context.t('create_coupon_store_limit_hint')),
), ),
], ],
); );
@ -324,9 +330,9 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('预览确认', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('create_coupon_step_preview'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('请确认以下信息,提交后将进入审核流程', style: TextStyle(color: AppColors.textSecondary)), Text(context.t('create_coupon_preview_desc'), style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 24), const SizedBox(height: 24),
// Preview Card // Preview Card
@ -345,16 +351,16 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'模板:${_selectedTemplate ?? '礼品卡'}', '${context.t('create_coupon_template_label')}${_selectedTemplate ?? context.t('create_coupon_tpl_gift')}',
style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: 0.8)), style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: 0.8)),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildPreviewStat('面值', '\$${_faceValueController.text.isNotEmpty ? _faceValueController.text : '25'}'), _buildPreviewStat(context.t('create_coupon_face_value_short'), '\$${_faceValueController.text.isNotEmpty ? _faceValueController.text : '25'}'),
_buildPreviewStat('发行价', '\$${_issuePriceController.text.isNotEmpty ? _issuePriceController.text : '21.25'}'), _buildPreviewStat(context.t('create_coupon_issue_price_short'), '\$${_issuePriceController.text.isNotEmpty ? _issuePriceController.text : '21.25'}'),
_buildPreviewStat('数量', _quantityController.text.isNotEmpty ? _quantityController.text : '5000'), _buildPreviewStat(context.t('create_coupon_quantity_short'), _quantityController.text.isNotEmpty ? _quantityController.text : '5000'),
], ],
), ),
], ],
@ -363,9 +369,9 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
const SizedBox(height: 20), const SizedBox(height: 20),
// Details List // Details List
_buildDetailRow('可转让', _transferable ? '是(最多${_maxResaleCount}次)' : ''), _buildDetailRow(context.t('create_coupon_transferable'), _transferable ? '${context.t('yes')}${context.t('create_coupon_max_times')}${_maxResaleCount}${context.t('create_coupon_times')}' : context.t('no')),
_buildDetailRow('退款窗口', '$_refundWindowDays'), _buildDetailRow(context.t('create_coupon_refund_window'), '$_refundWindowDays ${context.t('create_coupon_day_unit')}'),
_buildDetailRow('自动退款', _autoRefund ? '' : ''), _buildDetailRow(context.t('create_coupon_auto_refund'), _autoRefund ? context.t('yes') : context.t('no')),
const SizedBox(height: 20), const SizedBox(height: 20),
Container( Container(
@ -374,14 +380,14 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
color: AppColors.infoLight, color: AppColors.infoLight,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: const Row( child: Row(
children: [ children: [
Icon(Icons.info_outline_rounded, color: AppColors.info, size: 18), const Icon(Icons.info_outline_rounded, color: AppColors.info, size: 18),
SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'提交后将自动进入平台审核,审核通过后券将自动上架销售', context.t('create_coupon_review_notice'),
style: TextStyle(fontSize: 12, color: AppColors.info), style: const TextStyle(fontSize: 12, color: AppColors.info),
), ),
), ),
], ],
@ -427,7 +433,7 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
onPressed: () => setState(() => _currentStep--), onPressed: () => setState(() => _currentStep--),
child: const Text('上一步'), child: Text(context.t('prev_step')),
), ),
), ),
if (_currentStep > 0) const SizedBox(width: 12), if (_currentStep > 0) const SizedBox(width: 12),
@ -440,7 +446,7 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
_submitForReview(); _submitForReview();
} }
}, },
child: Text(_currentStep < 3 ? '下一步' : '提交审核'), child: Text(_currentStep < 3 ? context.t('next') : context.t('onboarding_submit_review')),
), ),
), ),
], ],
@ -452,15 +458,15 @@ class _CreateCouponPageState extends State<CreateCouponPage> {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('提交成功'), title: Text(context.t('create_coupon_submit_success')),
content: const Text('您的券已提交审核预计1-2个工作日内完成。'), content: Text(context.t('create_coupon_submit_desc')),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(ctx).pop(); Navigator.of(ctx).pop();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: const Text('确定'), child: Text(context.t('confirm')),
), ),
], ],
), ),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -11,36 +12,36 @@ class CreditPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('信用评级')), appBar: AppBar(title: Text(context.t('credit_title'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
children: [ children: [
// Score Gauge // Score Gauge
_buildScoreGauge(), _buildScoreGauge(context),
const SizedBox(height: 24), const SizedBox(height: 24),
// Four Factors // Four Factors
_buildFactorsCard(), _buildFactorsCard(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Tier Progress // Tier Progress
_buildTierProgress(), _buildTierProgress(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// AI Suggestions // AI Suggestions
_buildAiSuggestions(), _buildAiSuggestions(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Credit History // Credit History
_buildCreditHistory(), _buildCreditHistory(context),
], ],
), ),
), ),
); );
} }
Widget _buildScoreGauge() { Widget _buildScoreGauge(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -72,20 +73,20 @@ class CreditPage extends StatelessWidget {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('信用等级 AA', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), Text(context.t('credit_score_label'), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text('距离 AAA 等级还差 8 分', style: TextStyle(fontSize: 13, color: AppColors.textSecondary)), Text(context.t('credit_gap_label'), style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
], ],
), ),
); );
} }
Widget _buildFactorsCard() { Widget _buildFactorsCard(BuildContext context) {
final factors = [ final factors = [
('核销率', 0.85, 0.35, AppColors.success), (context.t('credit_factor_redemption'), 0.85, 0.35, AppColors.success),
('沉淀控制', 0.72, 0.25, AppColors.info), (context.t('credit_factor_breakage'), 0.72, 0.25, AppColors.info),
('市场存续', 0.90, 0.20, AppColors.primary), (context.t('credit_factor_market'), 0.90, 0.20, AppColors.primary),
('用户满意度', 0.78, 0.20, AppColors.warning), (context.t('credit_factor_satisfaction'), 0.78, 0.20, AppColors.warning),
]; ];
return Container( return Container(
@ -98,7 +99,7 @@ class CreditPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('评分因子', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('credit_factors'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16), const SizedBox(height: 16),
...factors.map((f) { ...factors.map((f) {
final (label, score, weight, color) = f; final (label, score, weight, color) = f;
@ -135,12 +136,12 @@ class CreditPage extends StatelessWidget {
); );
} }
Widget _buildTierProgress() { Widget _buildTierProgress(BuildContext context) {
final tiers = [ final tiers = [
('白银', AppColors.tierSilver, true), (context.t('credit_tier_silver'), AppColors.tierSilver, true),
('黄金', AppColors.tierGold, true), (context.t('credit_tier_gold'), AppColors.tierGold, true),
('铂金', AppColors.tierPlatinum, false), (context.t('credit_tier_platinum'), AppColors.tierPlatinum, false),
('钻石', AppColors.tierDiamond, false), (context.t('credit_tier_diamond'), AppColors.tierDiamond, false),
]; ];
return Container( return Container(
@ -153,7 +154,7 @@ class CreditPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('发行方层级', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('credit_tier_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
@ -189,16 +190,16 @@ class CreditPage extends StatelessWidget {
}).toList(), }).toList(),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( Text(
'当前:黄金 → 铂金需月发行量达500万', context.t('credit_tier_progress'),
style: TextStyle(fontSize: 12, color: AppColors.textSecondary), style: const TextStyle(fontSize: 12, color: AppColors.textSecondary),
), ),
], ],
), ),
); );
} }
Widget _buildAiSuggestions() { Widget _buildAiSuggestions(BuildContext context) {
final suggestions = [ final suggestions = [
('提升核销率', '建议在周末推出限时核销活动预计可提升核销率5%', Icons.trending_up_rounded), ('提升核销率', '建议在周末推出限时核销活动预计可提升核销率5%', Icons.trending_up_rounded),
('降低Breakage', '当前有12%的券过期未用建议到期前7天推送提醒', Icons.notification_important_rounded), ('降低Breakage', '当前有12%的券过期未用建议到期前7天推送提醒', Icons.notification_important_rounded),
@ -208,11 +209,11 @@ class CreditPage extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Row( Row(
children: [ children: [
Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20), const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20),
SizedBox(width: 8), const SizedBox(width: 8),
Text('AI 信用提升建议', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('credit_ai_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@ -247,7 +248,7 @@ class CreditPage extends StatelessWidget {
); );
} }
Widget _buildCreditHistory() { Widget _buildCreditHistory(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -258,7 +259,7 @@ class CreditPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('信用变动记录', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('credit_history_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildHistoryItem('信用分 +3', '核销率提升至85%', '2天前', AppColors.success), _buildHistoryItem('信用分 +3', '核销率提升至85%', '2天前', AppColors.success),
_buildHistoryItem('信用分 -1', 'Breakage率微升', '1周前', AppColors.error), _buildHistoryItem('信用分 -1', 'Breakage率微升', '1周前', AppColors.error),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -16,20 +17,20 @@ class QuotaManagementPage extends StatefulWidget {
} }
class _QuotaManagementPageState extends State<QuotaManagementPage> { class _QuotaManagementPageState extends State<QuotaManagementPage> {
String _selectedPeriod = '本月'; String _selectedPeriod = 'quota_period_month';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('配额管理'), title: Text(context.t('quota_title')),
actions: [ actions: [
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
// TODO: Show quota increase application dialog // TODO: Show quota increase application dialog
}, },
icon: const Icon(Icons.add_circle_outline_rounded, size: 18), icon: const Icon(Icons.add_circle_outline_rounded, size: 18),
label: const Text('申请提额'), label: Text(context.t('quota_apply_increase')),
), ),
], ],
), ),
@ -78,9 +79,9 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
children: [ children: [
const Icon(Icons.pie_chart_rounded, color: Colors.white, size: 22), const Icon(Icons.pie_chart_rounded, color: Colors.white, size: 22),
const SizedBox(width: 10), const SizedBox(width: 10),
const Text( Text(
'当前配额', context.t('quota_current'),
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: Colors.white), style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: Colors.white),
), ),
const Spacer(), const Spacer(),
Container( Container(
@ -89,8 +90,8 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
color: Colors.white.withValues(alpha: 0.2), color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(999), borderRadius: BorderRadius.circular(999),
), ),
child: const Text( child: Text(
'黄金层级', context.t('quota_gold_tier'),
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white), style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white),
), ),
), ),
@ -122,7 +123,7 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
style: TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: Colors.white), style: TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: Colors.white),
), ),
Text( Text(
'已使用', context.t('quota_used'),
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.7)), style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.7)),
), ),
], ],
@ -134,9 +135,9 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_buildQuotaStat('月发行限额', '\$5,000,000'), _buildQuotaStat(context.t('quota_monthly_limit'), '\$5,000,000'),
_buildQuotaStat('已使用', '\$3,100,000'), _buildQuotaStat(context.t('quota_used'), '\$3,100,000'),
_buildQuotaStat('剩余', '\$1,900,000'), _buildQuotaStat(context.t('quota_remaining'), '\$1,900,000'),
], ],
), ),
], ],
@ -178,7 +179,7 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('配额分配明细', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('quota_breakdown'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16), const SizedBox(height: 16),
...quotaTypes.map((q) { ...quotaTypes.map((q) {
final (name, used, total, color) = q; final (name, used, total, color) = q;
@ -217,21 +218,21 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
} }
Widget _buildPeriodSelector() { Widget _buildPeriodSelector() {
final periods = ['本月', '本季', '本年']; final periodKeys = ['quota_period_month', 'quota_period_quarter', 'quota_period_year'];
return Row( return Row(
children: [ children: [
const Text('使用记录', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('quota_usage_records'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const Spacer(), const Spacer(),
...periods.map((p) => Padding( ...periodKeys.map((key) => Padding(
padding: const EdgeInsets.only(left: 6), padding: const EdgeInsets.only(left: 6),
child: ChoiceChip( child: ChoiceChip(
label: Text(p, style: const TextStyle(fontSize: 12)), label: Text(context.t(key), style: const TextStyle(fontSize: 12)),
selected: _selectedPeriod == p, selected: _selectedPeriod == key,
onSelected: (_) => setState(() => _selectedPeriod = p), onSelected: (_) => setState(() => _selectedPeriod = key),
selectedColor: AppColors.primaryContainer, selectedColor: AppColors.primaryContainer,
labelStyle: TextStyle( labelStyle: TextStyle(
color: _selectedPeriod == p ? AppColors.primary : AppColors.textSecondary, color: _selectedPeriod == key ? AppColors.primary : AppColors.textSecondary,
fontWeight: _selectedPeriod == p ? FontWeight.w600 : FontWeight.w400, fontWeight: _selectedPeriod == key ? FontWeight.w600 : FontWeight.w400,
), ),
), ),
)), )),
@ -303,7 +304,7 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('层级与配额', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('quota_tier_and_quota'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16), const SizedBox(height: 16),
...tiers.map((t) { ...tiers.map((t) {
final (nameEn, nameCn, quota, reached, color) = t; final (nameEn, nameCn, quota, reached, color) = t;
@ -346,13 +347,13 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
color: color, color: color,
borderRadius: BorderRadius.circular(999), borderRadius: BorderRadius.circular(999),
), ),
child: const Text('当前', style: TextStyle(fontSize: 9, color: Colors.white, fontWeight: FontWeight.w700)), child: Text(context.t('quota_current_badge'), style: const TextStyle(fontSize: 9, color: Colors.white, fontWeight: FontWeight.w700)),
), ),
], ],
], ],
), ),
Text( Text(
'月发行配额: $quota', '${context.t('quota_monthly_quota')}: $quota',
style: const TextStyle(fontSize: 11, color: AppColors.textSecondary), style: const TextStyle(fontSize: 11, color: AppColors.textSecondary),
), ),
], ],
@ -373,14 +374,14 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
color: AppColors.primarySurface, color: AppColors.primarySurface,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Row( child: Row(
children: [ children: [
Icon(Icons.trending_up_rounded, color: AppColors.primary, size: 18), const Icon(Icons.trending_up_rounded, color: AppColors.primary, size: 18),
SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'距铂金升级: 信用分达90+ 且 月发行量连续3月 ≥\$10M', context.t('quota_upgrade_hint'),
style: TextStyle(fontSize: 12, color: AppColors.primary), style: const TextStyle(fontSize: 12, color: AppColors.primary),
), ),
), ),
], ],
@ -402,11 +403,11 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('提额申请记录', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('quota_request_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildRequestItem('REQ-001', '临时提额: +\$2M', '2026-02-05', '审核中', AppColors.warning), _buildRequestItem('REQ-001', '临时提额: +\$2M', '2026-02-05', context.t('quota_request_reviewing'), AppColors.warning),
_buildRequestItem('REQ-002', '长期提额: Gold→Platinum', '2026-01-20', '已驳回', AppColors.error), _buildRequestItem('REQ-002', '长期提额: Gold→Platinum', '2026-01-20', context.t('quota_request_rejected'), AppColors.error),
_buildRequestItem('REQ-003', '临时提额: +\$500K (春节活动)', '2026-01-15', '已批准', AppColors.success), _buildRequestItem('REQ-003', '临时提额: +\$500K (春节活动)', '2026-01-15', context.t('quota_request_approved'), AppColors.success),
const SizedBox(height: 12), const SizedBox(height: 12),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
@ -415,7 +416,7 @@ class _QuotaManagementPageState extends State<QuotaManagementPage> {
// TODO: Show new quota increase application dialog // TODO: Show new quota increase application dialog
}, },
icon: const Icon(Icons.add_rounded, size: 18), icon: const Icon(Icons.add_rounded, size: 18),
label: const Text('提交新申请'), label: Text(context.t('quota_submit_new')),
), ),
), ),
], ],

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/router.dart'; import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -13,7 +14,7 @@ class IssuerDashboardPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('数据概览'), title: Text(context.t('tab_dashboard')),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.notifications_outlined), icon: const Icon(Icons.notifications_outlined),
@ -29,11 +30,11 @@ class IssuerDashboardPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Issuer Info Card // Issuer Info Card
_buildIssuerInfoCard(), _buildIssuerInfoCard(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Stats Grid (2x2) // Stats Grid (2x2)
_buildStatsGrid(), _buildStatsGrid(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// AI Insight Card // AI Insight Card
@ -45,18 +46,18 @@ class IssuerDashboardPage extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
// Sales Trend Chart Placeholder // Sales Trend Chart Placeholder
_buildSalesTrendCard(), _buildSalesTrendCard(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Recent Activity // Recent Activity
_buildRecentActivity(), _buildRecentActivity(context),
], ],
), ),
), ),
); );
} }
Widget _buildIssuerInfoCard() { Widget _buildIssuerInfoCard(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -90,9 +91,9 @@ class IssuerDashboardPage extends StatelessWidget {
color: AppColors.tierGold.withValues(alpha: 0.3), color: AppColors.tierGold.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(999), borderRadius: BorderRadius.circular(999),
), ),
child: const Text( child: Text(
'黄金发行方', context.t('dashboard_gold_issuer'),
style: TextStyle(fontSize: 11, color: Colors.white, fontWeight: FontWeight.w600), style: const TextStyle(fontSize: 11, color: Colors.white, fontWeight: FontWeight.w600),
), ),
), ),
], ],
@ -104,12 +105,12 @@ class IssuerDashboardPage extends StatelessWidget {
); );
} }
Widget _buildStatsGrid() { Widget _buildStatsGrid(BuildContext context) {
final stats = [ final stats = [
('总发行量', '12,580', Icons.confirmation_number_rounded, AppColors.primary), (context.t('dashboard_total_issued'), '12,580', Icons.confirmation_number_rounded, AppColors.primary),
('核销率', '78.5%', Icons.check_circle_rounded, AppColors.success), (context.t('dashboard_redemption_rate'), '78.5%', Icons.check_circle_rounded, AppColors.success),
('销售收入', '\$125,800', Icons.attach_money_rounded, AppColors.info), (context.t('dashboard_sales_revenue'), '\$125,800', Icons.attach_money_rounded, AppColors.info),
('可提现', '\$42,300', Icons.account_balance_wallet_rounded, AppColors.warning), (context.t('dashboard_withdrawable'), '\$42,300', Icons.account_balance_wallet_rounded, AppColors.warning),
]; ];
return GridView.builder( return GridView.builder(
@ -164,17 +165,17 @@ class IssuerDashboardPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Row( Row(
children: [ children: [
Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20), const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20),
SizedBox(width: 8), const SizedBox(width: 8),
Text('AI 洞察', style: TextStyle(fontSize: 14, color: AppColors.primary, fontWeight: FontWeight.w600)), Text(context.t('dashboard_ai_insight'), style: const TextStyle(fontSize: 14, color: AppColors.primary, fontWeight: FontWeight.w600)),
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
const Text( Text(
'您的 ¥25 礼品卡核销率达到 92%,远高于同类平均。建议增发 500 张以满足市场需求。', context.t('dashboard_ai_insight_content'),
style: TextStyle(fontSize: 13, color: AppColors.textSecondary, height: 1.5), style: const TextStyle(fontSize: 13, color: AppColors.textSecondary, height: 1.5),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
@ -187,7 +188,7 @@ class IssuerDashboardPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
minimumSize: Size.zero, minimumSize: Size.zero,
), ),
child: const Text('忽略', style: TextStyle(fontSize: 13)), child: Text(context.t('dashboard_dismiss'), style: const TextStyle(fontSize: 13)),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
@ -198,7 +199,7 @@ class IssuerDashboardPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
minimumSize: Size.zero, minimumSize: Size.zero,
), ),
child: const Text('采纳建议', style: TextStyle(fontSize: 13)), child: Text(context.t('dashboard_accept'), style: const TextStyle(fontSize: 13)),
), ),
], ],
), ),
@ -232,12 +233,12 @@ class IssuerDashboardPage extends StatelessWidget {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
const Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('信用等级 AA', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('dashboard_credit_rating'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
Text('距离 AAA 还差 12 分', style: TextStyle(fontSize: 12, color: AppColors.textTertiary)), Text(context.t('dashboard_credit_gap'), style: const TextStyle(fontSize: 12, color: AppColors.textTertiary)),
], ],
), ),
), ),
@ -245,7 +246,7 @@ class IssuerDashboardPage extends StatelessWidget {
onPressed: () { onPressed: () {
Navigator.pushNamed(context, AppRouter.credit); Navigator.pushNamed(context, AppRouter.credit);
}, },
child: const Text('提升建议'), child: Text(context.t('dashboard_improve_suggestion')),
), ),
], ],
), ),
@ -254,11 +255,11 @@ class IssuerDashboardPage extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// Quota Progress // Quota Progress
const Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('发行额度', style: TextStyle(fontSize: 13, color: AppColors.textSecondary)), Text(context.t('dashboard_issue_quota'), style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
Text('\$380,000 / \$500,000', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600)), const Text('\$380,000 / \$500,000', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -272,16 +273,16 @@ class IssuerDashboardPage extends StatelessWidget {
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Text('已用 76%', style: TextStyle(fontSize: 11, color: AppColors.textTertiary)), child: Text(context.t('dashboard_used_percent'), style: const TextStyle(fontSize: 11, color: AppColors.textTertiary)),
), ),
], ],
), ),
); );
} }
Widget _buildSalesTrendCard() { Widget _buildSalesTrendCard(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -292,11 +293,11 @@ class IssuerDashboardPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('销售趋势', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('dashboard_sales_trend'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
Text('近7天', style: TextStyle(fontSize: 12, color: AppColors.primary)), Text(context.t('dashboard_last_7_days'), style: const TextStyle(fontSize: 12, color: AppColors.primary)),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -307,8 +308,8 @@ class IssuerDashboardPage extends StatelessWidget {
color: AppColors.gray50, color: AppColors.gray50,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Center( child: Center(
child: Text('销售趋势图表', style: TextStyle(color: AppColors.textTertiary)), child: Text(context.t('dashboard_sales_chart'), style: const TextStyle(color: AppColors.textTertiary)),
), ),
), ),
], ],
@ -316,7 +317,7 @@ class IssuerDashboardPage extends StatelessWidget {
); );
} }
Widget _buildRecentActivity() { Widget _buildRecentActivity(BuildContext context) {
final activities = [ final activities = [
('¥25 礼品卡', '售出 15 张', '2分钟前', AppColors.success), ('¥25 礼品卡', '售出 15 张', '2分钟前', AppColors.success),
('¥100 购物券', '核销 8 张', '15分钟前', AppColors.info), ('¥100 购物券', '核销 8 张', '15分钟前', AppColors.info),
@ -333,7 +334,7 @@ class IssuerDashboardPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('最近动态', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('dashboard_recent_activity'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 12), const SizedBox(height: 12),
...activities.map((a) { ...activities.map((a) {
final (title, desc, time, color) = a; final (title, desc, time, color) = a;

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -17,14 +18,14 @@ class UserPortraitPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('用户画像'), title: Text(context.t('user_portrait_title')),
actions: [ actions: [
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
// TODO: Export user portrait data // TODO: Export user portrait data
}, },
icon: const Icon(Icons.file_download_outlined, size: 18), icon: const Icon(Icons.file_download_outlined, size: 18),
label: const Text('导出'), label: Text(context.t('export')),
), ),
], ],
), ),
@ -34,39 +35,39 @@ class UserPortraitPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// User Stats Summary // User Stats Summary
_buildUserStatsSummary(), _buildUserStatsSummary(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Age Distribution // Age Distribution
_buildAgeDistribution(), _buildAgeDistribution(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Geographic Distribution // Geographic Distribution
_buildGeoDistribution(), _buildGeoDistribution(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Purchase Preference // Purchase Preference
_buildPurchasePreference(), _buildPurchasePreference(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Repurchase Analysis // Repurchase Analysis
_buildRepurchaseAnalysis(), _buildRepurchaseAnalysis(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// AI Insight // AI Insight
_buildAiInsight(), _buildAiInsight(context),
], ],
), ),
), ),
); );
} }
Widget _buildUserStatsSummary() { Widget _buildUserStatsSummary(BuildContext context) {
final stats = [ final stats = [
('总购买用户', '12,456', Icons.people_alt_rounded, AppColors.primary), (context.t('user_portrait_total_buyers'), '12,456', Icons.people_alt_rounded, AppColors.primary),
('月活用户', '3,281', Icons.trending_up_rounded, AppColors.success), (context.t('user_portrait_mau'), '3,281', Icons.trending_up_rounded, AppColors.success),
('平均客单价', '\$23.5', Icons.attach_money_rounded, AppColors.info), (context.t('user_portrait_avg_price'), '\$23.5', Icons.attach_money_rounded, AppColors.info),
('复购率', '34.2%', Icons.replay_rounded, AppColors.warning), (context.t('user_portrait_repurchase_rate'), '34.2%', Icons.replay_rounded, AppColors.warning),
]; ];
return Row( return Row(
@ -107,7 +108,7 @@ class UserPortraitPage extends StatelessWidget {
); );
} }
Widget _buildAgeDistribution() { Widget _buildAgeDistribution(BuildContext context) {
final ages = [ final ages = [
('18-24', 0.15, '15%'), ('18-24', 0.15, '15%'),
('25-34', 0.38, '38%'), ('25-34', 0.38, '38%'),
@ -118,7 +119,7 @@ class UserPortraitPage extends StatelessWidget {
return _card( return _card(
icon: Icons.cake_rounded, icon: Icons.cake_rounded,
title: '年龄分布', title: context.t('user_portrait_age_dist'),
child: Column( child: Column(
children: ages.map((a) { children: ages.map((a) {
final (range, pct, label) = a; final (range, pct, label) = a;
@ -164,7 +165,7 @@ class UserPortraitPage extends StatelessWidget {
); );
} }
Widget _buildGeoDistribution() { Widget _buildGeoDistribution(BuildContext context) {
final regions = [ final regions = [
('加利福尼亚', 2845, AppColors.primary), ('加利福尼亚', 2845, AppColors.primary),
('纽约', 2134, AppColors.info), ('纽约', 2134, AppColors.info),
@ -177,7 +178,7 @@ class UserPortraitPage extends StatelessWidget {
return _card( return _card(
icon: Icons.location_on_rounded, icon: Icons.location_on_rounded,
title: '地域分布 (Top 5)', title: context.t('user_portrait_geo_dist'),
child: Column( child: Column(
children: regions.map((r) { children: regions.map((r) {
final (name, count, color) = r; final (name, count, color) = r;
@ -229,18 +230,18 @@ class UserPortraitPage extends StatelessWidget {
); );
} }
Widget _buildPurchasePreference() { Widget _buildPurchasePreference(BuildContext context) {
final categories = [ final categories = [
('餐饮', 42, AppColors.couponDining, Icons.restaurant_rounded), (context.t('user_portrait_dining'), 42, AppColors.couponDining, Icons.restaurant_rounded),
('购物', 28, AppColors.couponShopping, Icons.shopping_bag_rounded), (context.t('user_portrait_shopping'), 28, AppColors.couponShopping, Icons.shopping_bag_rounded),
('娱乐', 15, AppColors.couponEntertainment, Icons.movie_rounded), (context.t('user_portrait_entertainment'), 15, AppColors.couponEntertainment, Icons.movie_rounded),
('旅行', 10, AppColors.couponTravel, Icons.flight_rounded), (context.t('user_portrait_travel'), 10, AppColors.couponTravel, Icons.flight_rounded),
('其他', 5, AppColors.couponOther, Icons.more_horiz_rounded), (context.t('user_portrait_other'), 5, AppColors.couponOther, Icons.more_horiz_rounded),
]; ];
return _card( return _card(
icon: Icons.favorite_rounded, icon: Icons.favorite_rounded,
title: '消费偏好', title: context.t('user_portrait_preference'),
child: Wrap( child: Wrap(
spacing: 10, spacing: 10,
runSpacing: 10, runSpacing: 10,
@ -291,18 +292,18 @@ class UserPortraitPage extends StatelessWidget {
); );
} }
Widget _buildRepurchaseAnalysis() { Widget _buildRepurchaseAnalysis(BuildContext context) {
final cohorts = [ final cohorts = [
('首次购买', 12456, '100%', AppColors.gray400), (context.t('user_portrait_first_purchase'), 12456, '100%', AppColors.gray400),
('2次购买', 4260, '34.2%', AppColors.primaryLight), (context.t('user_portrait_2nd_purchase'), 4260, '34.2%', AppColors.primaryLight),
('3-5次', 2134, '17.1%', AppColors.primary), (context.t('user_portrait_3_5_purchase'), 2134, '17.1%', AppColors.primary),
('6-10次', 856, '6.9%', AppColors.primaryDark), (context.t('user_portrait_6_10_purchase'), 856, '6.9%', AppColors.primaryDark),
('10次以上', 312, '2.5%', AppColors.primaryDark), (context.t('user_portrait_10_plus_purchase'), 312, '2.5%', AppColors.primaryDark),
]; ];
return _card( return _card(
icon: Icons.replay_circle_filled_rounded, icon: Icons.replay_circle_filled_rounded,
title: '复购漏斗', title: context.t('user_portrait_repurchase_funnel'),
child: Column( child: Column(
children: cohorts.asMap().entries.map((entry) { children: cohorts.asMap().entries.map((entry) {
final i = entry.key; final i = entry.key;
@ -348,12 +349,12 @@ class UserPortraitPage extends StatelessWidget {
); );
} }
Widget _buildAiInsight() { Widget _buildAiInsight(BuildContext context) {
final insights = [ final insights = [
('核心用户群体', '25-34岁加利福尼亚用户偏好餐饮类券客单价\$28.5高于整体均值21%'), (context.t('user_portrait_core_users'), context.t('user_portrait_core_users_desc')),
('复购提升建议', '针对首次购买用户7天内推送同品牌关联券可将复购率提升至42%'), (context.t('user_portrait_repurchase_advice'), context.t('user_portrait_repurchase_advice_desc')),
('地域扩展机会', '德州、佛州用户增长率最快(+35% MoM),建议增加当地品牌合作'), (context.t('user_portrait_geo_opportunity'), context.t('user_portrait_geo_opportunity_desc')),
('流失预警', '30天未活跃用户占比18%,建议发放专属回归优惠券'), (context.t('user_portrait_churn_warning'), context.t('user_portrait_churn_warning_desc')),
]; ];
return Container( return Container(
@ -379,8 +380,8 @@ class UserPortraitPage extends StatelessWidget {
Text('', style: TextStyle(fontSize: 14))), Text('', style: TextStyle(fontSize: 14))),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
const Text('AI 用户洞察', Text(context.t('user_portrait_ai_insight'),
style: TextStyle( style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w600)), fontSize: 15, fontWeight: FontWeight.w600)),
], ],
), ),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/router.dart'; import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -15,18 +16,18 @@ class FinancePage extends StatelessWidget {
length: 3, length: 3,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('财务管理'), title: Text(context.t('finance_title')),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.download_rounded), icon: const Icon(Icons.download_rounded),
onPressed: () => _showExportDialog(context), onPressed: () => _showExportDialog(context),
), ),
], ],
bottom: const TabBar( bottom: TabBar(
tabs: [ tabs: [
Tab(text: '概览'), Tab(text: context.t('finance_tab_overview')),
Tab(text: '交易明细'), Tab(text: context.t('finance_tab_transactions')),
Tab(text: '对账报表'), Tab(text: context.t('finance_tab_reconciliation')),
], ],
), ),
), ),
@ -45,11 +46,11 @@ class FinancePage extends StatelessWidget {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => SimpleDialog( builder: (ctx) => SimpleDialog(
title: const Text('导出数据'), title: Text(context.t('finance_export_title')),
children: [ children: [
SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: const Text('导出 CSV')), SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: Text(context.t('finance_export_csv'))),
SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: const Text('导出 Excel')), SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: Text(context.t('finance_export_excel'))),
SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: const Text('导出 PDF')), SimpleDialogOption(onPressed: () => Navigator.pop(ctx), child: Text(context.t('finance_export_pdf'))),
], ],
), ),
); );
@ -75,7 +76,7 @@ class _OverviewTab extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('可提现余额', style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: 0.7))), Text(context.t('finance_withdrawable'), style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: 0.7))),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text('\$42,300.00', style: TextStyle(fontSize: 32, fontWeight: FontWeight.w700, color: Colors.white)), const Text('\$42,300.00', style: TextStyle(fontSize: 32, fontWeight: FontWeight.w700, color: Colors.white)),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -89,7 +90,7 @@ class _OverviewTab extends StatelessWidget {
backgroundColor: Colors.white, backgroundColor: Colors.white,
foregroundColor: AppColors.primary, foregroundColor: AppColors.primary,
), ),
child: const Text('提现'), child: Text(context.t('finance_withdraw')),
), ),
), ),
], ],
@ -98,11 +99,11 @@ class _OverviewTab extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
// Financial Stats // Financial Stats
_buildFinanceStatsGrid(), _buildFinanceStatsGrid(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Guarantee Fund // Guarantee Fund
_buildGuaranteeFundCard(), _buildGuaranteeFundCard(context),
const SizedBox(height: 20), const SizedBox(height: 20),
// Revenue Trend // Revenue Trend
@ -116,7 +117,7 @@ class _OverviewTab extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('收入趋势', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('finance_revenue_trend'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
height: 160, height: 160,
@ -124,7 +125,7 @@ class _OverviewTab extends StatelessWidget {
color: AppColors.gray50, color: AppColors.gray50,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Center(child: Text('月度收入趋势图', style: TextStyle(color: AppColors.textTertiary))), child: Center(child: Text(context.t('finance_revenue_chart'), style: const TextStyle(color: AppColors.textTertiary))),
), ),
], ],
), ),
@ -134,14 +135,14 @@ class _OverviewTab extends StatelessWidget {
); );
} }
Widget _buildFinanceStatsGrid() { Widget _buildFinanceStatsGrid(BuildContext context) {
final stats = [ final stats = [
('销售收入', '\$125,800', AppColors.success), (context.t('finance_sales_income'), '\$125,800', AppColors.success),
('Breakage收入', '\$8,200', AppColors.info), (context.t('finance_breakage_income'), '\$8,200', AppColors.info),
('平台手续费', '-\$1,510', AppColors.error), (context.t('finance_platform_fee'), '-\$1,510', AppColors.error),
('待结算', '\$15,400', AppColors.warning), (context.t('finance_pending_settlement'), '\$15,400', AppColors.warning),
('已提现', '\$66,790', AppColors.textSecondary), (context.t('finance_withdrawn'), '\$66,790', AppColors.textSecondary),
('总收入', '\$132,490', AppColors.primary), (context.t('finance_total_income'), '\$132,490', AppColors.primary),
]; ];
return GridView.builder( return GridView.builder(
@ -176,7 +177,7 @@ class _OverviewTab extends StatelessWidget {
); );
} }
Widget _buildGuaranteeFundCard() { Widget _buildGuaranteeFundCard(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -187,21 +188,21 @@ class _OverviewTab extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Row( Row(
children: [ children: [
Icon(Icons.shield_rounded, color: AppColors.info, size: 20), const Icon(Icons.shield_rounded, color: AppColors.info, size: 20),
SizedBox(width: 8), const SizedBox(width: 8),
Text('保证金与冻结款', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('finance_guarantee_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildRow('已缴纳保证金', '\$10,000'), _buildRow(context.t('finance_guarantee_deposit'), '\$10,000'),
_buildRow('冻结销售款', '\$5,200'), _buildRow(context.t('finance_frozen_sales'), '\$5,200'),
_buildRow('冻结比例', '20%'), _buildRow(context.t('finance_frozen_ratio'), '20%'),
const SizedBox(height: 12), const SizedBox(height: 12),
SwitchListTile( SwitchListTile(
title: const Text('自动冻结销售款', style: TextStyle(fontSize: 14)), title: Text(context.t('finance_auto_freeze'), style: const TextStyle(fontSize: 14)),
subtitle: const Text('开启后自动冻结20%销售额以提升信用'), subtitle: Text(context.t('finance_auto_freeze_desc')),
value: true, value: true,
onChanged: (_) { onChanged: (_) {
// TODO: Toggle auto-freeze setting // TODO: Toggle auto-freeze setting
@ -282,7 +283,7 @@ class _ReconciliationTab extends StatelessWidget {
// TODO: Trigger reconciliation report generation // TODO: Trigger reconciliation report generation
}, },
icon: const Icon(Icons.add_rounded), icon: const Icon(Icons.add_rounded),
label: const Text('生成新对账单'), label: Text(context.t('finance_generate_report')),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
minimumSize: const Size(double.infinity, 48), minimumSize: const Size(double.infinity, 48),
), ),
@ -326,14 +327,14 @@ class _ReconciliationTab extends StatelessWidget {
// TODO: Navigate to reconciliation detail view // TODO: Navigate to reconciliation detail view
}, },
icon: const Icon(Icons.visibility_rounded, size: 16), icon: const Icon(Icons.visibility_rounded, size: 16),
label: const Text('查看'), label: Text(context.t('view')),
), ),
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
// TODO: Export reconciliation report // TODO: Export reconciliation report
}, },
icon: const Icon(Icons.download_rounded, size: 16), icon: const Icon(Icons.download_rounded, size: 16),
label: const Text('导出'), label: Text(context.t('export')),
), ),
], ],
), ),

View File

@ -3,6 +3,7 @@ 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 '../../../../app/router.dart'; import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -15,21 +16,21 @@ class FinancingAnalysisPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('融资效果分析'), title: Text(context.t('financing_title')),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.refresh_rounded), icon: const Icon(Icons.refresh_rounded),
onPressed: () { onPressed: () {
// TODO: Refresh financing analysis data // TODO: Refresh financing analysis data
}, },
tooltip: '刷新数据', tooltip: context.t('financing_refresh_tooltip'),
), ),
IconButton( IconButton(
icon: const Icon(Icons.download_rounded), icon: const Icon(Icons.download_rounded),
onPressed: () { onPressed: () {
// TODO: Export financing analysis report // TODO: Export financing analysis report
}, },
tooltip: '导出报告', tooltip: context.t('financing_export_tooltip'),
), ),
], ],
), ),
@ -39,23 +40,23 @@ class FinancingAnalysisPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Stats Cards // Stats Cards
_buildStatsCards(), _buildStatsCards(context),
const SizedBox(height: 24), const SizedBox(height: 24),
// Financing Timeline // Financing Timeline
_buildFinancingTimeline(), _buildFinancingTimeline(context),
const SizedBox(height: 24), const SizedBox(height: 24),
// Cost-Benefit Analysis // Cost-Benefit Analysis
_buildCostBenefitCard(), _buildCostBenefitCard(context),
const SizedBox(height: 24), const SizedBox(height: 24),
// Liquidity Metrics // Liquidity Metrics
_buildLiquidityMetrics(), _buildLiquidityMetrics(context),
const SizedBox(height: 24), const SizedBox(height: 24),
// Risk Indicators // Risk Indicators
_buildRiskIndicators(), _buildRiskIndicators(context),
const SizedBox(height: 24), const SizedBox(height: 24),
// AI Recommendation // AI Recommendation
@ -70,12 +71,12 @@ class FinancingAnalysisPage extends StatelessWidget {
// Stats Cards // Stats Cards
// ============================================================ // ============================================================
Widget _buildStatsCards() { Widget _buildStatsCards(BuildContext context) {
final stats = [ final stats = [
('融资总额', '\$2,850,000', AppColors.primary, Icons.account_balance_rounded, '+12.5%'), (context.t('financing_total_amount'), '\$2,850,000', AppColors.primary, Icons.account_balance_rounded, '+12.5%'),
('平均利率', '4.2%', AppColors.info, Icons.percent_rounded, '-0.3%'), (context.t('financing_avg_rate'), '4.2%', AppColors.info, Icons.percent_rounded, '-0.3%'),
('融资笔数', '18', AppColors.success, Icons.receipt_long_rounded, '+3'), (context.t('financing_count'), '18', AppColors.success, Icons.receipt_long_rounded, '+3'),
('资金利用率', '87.6%', AppColors.warning, Icons.pie_chart_rounded, '+5.2%'), (context.t('financing_utilization'), '87.6%', AppColors.warning, Icons.pie_chart_rounded, '+5.2%'),
]; ];
return GridView.builder( return GridView.builder(
@ -149,14 +150,14 @@ class FinancingAnalysisPage extends StatelessWidget {
// Financing Timeline // Financing Timeline
// ============================================================ // ============================================================
Widget _buildFinancingTimeline() { Widget _buildFinancingTimeline(BuildContext context) {
final stages = [ final stages = [
('申请提交', '2026-01-15', true, AppColors.success), (context.t('financing_stage_submitted'), '2026-01-15', true, AppColors.success),
('审批通过', '2026-01-17', true, AppColors.success), (context.t('financing_stage_approved'), '2026-01-17', true, AppColors.success),
('资金到账', '2026-01-18', true, AppColors.success), (context.t('financing_stage_funded'), '2026-01-18', true, AppColors.success),
('使用中', '2026-01-18 ~', true, AppColors.primary), (context.t('financing_stage_in_use'), '2026-01-18 ~', true, AppColors.primary),
('还款期', '2026-07-18', false, AppColors.textTertiary), (context.t('financing_stage_repayment'), '2026-07-18', false, AppColors.textTertiary),
('结清', '待定', false, AppColors.textTertiary), (context.t('financing_stage_settled'), context.t('financing_stage_tbd'), false, AppColors.textTertiary),
]; ];
return Container( return Container(
@ -173,7 +174,7 @@ class FinancingAnalysisPage extends StatelessWidget {
children: [ children: [
const Icon(Icons.timeline_rounded, color: AppColors.primary, size: 20), const Icon(Icons.timeline_rounded, color: AppColors.primary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('融资生命周期', style: AppTypography.h3), Text(context.t('financing_lifecycle'), style: AppTypography.h3),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@ -243,7 +244,7 @@ class FinancingAnalysisPage extends StatelessWidget {
// Cost-Benefit Analysis // Cost-Benefit Analysis
// ============================================================ // ============================================================
Widget _buildCostBenefitCard() { Widget _buildCostBenefitCard(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -258,7 +259,7 @@ class FinancingAnalysisPage extends StatelessWidget {
children: [ children: [
const Icon(Icons.analytics_rounded, color: AppColors.primary, size: 20), const Icon(Icons.analytics_rounded, color: AppColors.primary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('成本效益分析', style: AppTypography.h3), Text(context.t('financing_cost_benefit'), style: AppTypography.h3),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@ -268,9 +269,9 @@ class FinancingAnalysisPage extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: _buildCostBenefitItem( child: _buildCostBenefitItem(
'利息成本', context.t('financing_interest_cost'),
'\$119,700', '\$119,700',
'年化 4.2%', '${context.t('financing_annual_rate')} 4.2%',
AppColors.error, AppColors.error,
Icons.trending_down_rounded, Icons.trending_down_rounded,
), ),
@ -278,9 +279,9 @@ class FinancingAnalysisPage extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: _buildCostBenefitItem( child: _buildCostBenefitItem(
'产生收入', context.t('financing_generated_income'),
'\$458,200', '\$458,200',
'利用融资收入', context.t('financing_income_from_financing'),
AppColors.success, AppColors.success,
Icons.trending_up_rounded, Icons.trending_up_rounded,
), ),
@ -300,7 +301,7 @@ class FinancingAnalysisPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'净收益', context.t('financing_net_benefit'),
style: AppTypography.labelMedium.copyWith(color: Colors.white), style: AppTypography.labelMedium.copyWith(color: Colors.white),
), ),
Text( Text(
@ -316,7 +317,7 @@ class FinancingAnalysisPage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('投资回报率 (ROI)', style: AppTypography.bodySmall), Text(context.t('financing_roi'), style: AppTypography.bodySmall),
Text( Text(
'282.7%', '282.7%',
style: AppTypography.labelLarge.copyWith(color: AppColors.success), style: AppTypography.labelLarge.copyWith(color: AppColors.success),
@ -327,7 +328,7 @@ class FinancingAnalysisPage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('收益/成本比', style: AppTypography.bodySmall), Text(context.t('financing_cost_ratio'), style: AppTypography.bodySmall),
Text( Text(
'3.83x', '3.83x',
style: AppTypography.labelLarge.copyWith(color: AppColors.primary), style: AppTypography.labelLarge.copyWith(color: AppColors.primary),
@ -376,7 +377,7 @@ class FinancingAnalysisPage extends StatelessWidget {
// Liquidity Metrics // Liquidity Metrics
// ============================================================ // ============================================================
Widget _buildLiquidityMetrics() { Widget _buildLiquidityMetrics(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -391,16 +392,16 @@ class FinancingAnalysisPage extends StatelessWidget {
children: [ children: [
const Icon(Icons.water_drop_rounded, color: AppColors.info, size: 20), const Icon(Icons.water_drop_rounded, color: AppColors.info, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('流动性指标', style: AppTypography.h3), Text(context.t('financing_liquidity'), style: AppTypography.h3),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Quick Ratio // Quick Ratio
_buildMetricRow( _buildMetricRow(
'速动比率 (Quick Ratio)', context.t('financing_quick_ratio'),
'1.85', '1.85',
'健康', context.t('financing_status_healthy'),
AppColors.success, AppColors.success,
0.85, 0.85,
), ),
@ -408,16 +409,16 @@ class FinancingAnalysisPage extends StatelessWidget {
// Current Ratio // Current Ratio
_buildMetricRow( _buildMetricRow(
'流动比率 (Current Ratio)', context.t('financing_current_ratio'),
'2.34', '2.34',
'良好', context.t('financing_status_good'),
AppColors.success, AppColors.success,
0.78, 0.78,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Cash Flow Forecast // Cash Flow Forecast
_buildSectionTitle('现金流预测'), _buildSectionTitle(context.t('financing_cashflow_forecast')),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
@ -427,17 +428,17 @@ class FinancingAnalysisPage extends StatelessWidget {
), ),
child: Column( child: Column(
children: [ children: [
_buildForecastRow('本月预计流入', '+\$85,200', AppColors.success), _buildForecastRow(context.t('financing_this_month_inflow'), '+\$85,200', AppColors.success),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildForecastRow('本月预计流出', '-\$52,800', AppColors.error), _buildForecastRow(context.t('financing_this_month_outflow'), '-\$52,800', AppColors.error),
const Divider(height: 16), const Divider(height: 16),
_buildForecastRow('净现金流', '+\$32,400', AppColors.primary), _buildForecastRow(context.t('financing_net_cashflow'), '+\$32,400', AppColors.primary),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildForecastRow('下月预计流入', '+\$92,500', AppColors.success), _buildForecastRow(context.t('financing_next_month_inflow'), '+\$92,500', AppColors.success),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildForecastRow('下月预计流出', '-\$68,300', AppColors.error), _buildForecastRow(context.t('financing_next_month_outflow'), '-\$68,300', AppColors.error),
const Divider(height: 16), const Divider(height: 16),
_buildForecastRow('下月净现金流', '+\$24,200', AppColors.primary), _buildForecastRow(context.t('financing_next_month_net'), '+\$24,200', AppColors.primary),
], ],
), ),
), ),
@ -509,7 +510,7 @@ class FinancingAnalysisPage extends StatelessWidget {
// Risk Indicators // Risk Indicators
// ============================================================ // ============================================================
Widget _buildRiskIndicators() { Widget _buildRiskIndicators(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -524,7 +525,7 @@ class FinancingAnalysisPage extends StatelessWidget {
children: [ children: [
const Icon(Icons.shield_rounded, color: AppColors.warning, size: 20), const Icon(Icons.shield_rounded, color: AppColors.warning, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('风险指标', style: AppTypography.h3), Text(context.t('financing_risk_title'), style: AppTypography.h3),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@ -532,36 +533,36 @@ class FinancingAnalysisPage extends StatelessWidget {
// Risk gauge cards // Risk gauge cards
Row( Row(
children: [ children: [
Expanded(child: _buildRiskGauge('违约率', '0.8%', AppColors.success, 0.08)), Expanded(child: _buildRiskGauge(context.t('financing_default_rate'), '0.8%', AppColors.success, 0.08)),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded(child: _buildRiskGauge('逾期率', '2.3%', AppColors.warning, 0.23)), Expanded(child: _buildRiskGauge(context.t('financing_overdue_rate'), '2.3%', AppColors.warning, 0.23)),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded(child: _buildRiskGauge('集中度', '35%', AppColors.info, 0.35)), Expanded(child: _buildRiskGauge(context.t('financing_concentration'), '35%', AppColors.info, 0.35)),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Risk detail rows // Risk detail rows
_buildRiskDetailRow( _buildRiskDetailRow(
'违约率 (Default Rate)', context.t('financing_default_detail'),
'0.8%', '0.8%',
'低于行业平均 1.5%', context.t('financing_default_desc'),
AppColors.success, AppColors.success,
Icons.check_circle_rounded, Icons.check_circle_rounded,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildRiskDetailRow( _buildRiskDetailRow(
'逾期率 (Overdue Rate)', context.t('financing_overdue_detail'),
'2.3%', '2.3%',
'接近预警线 3.0%,需关注', context.t('financing_overdue_desc'),
AppColors.warning, AppColors.warning,
Icons.warning_rounded, Icons.warning_rounded,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildRiskDetailRow( _buildRiskDetailRow(
'集中度风险 (Concentration)', context.t('financing_concentration_detail'),
'35%', '35%',
'最大单笔占比 35%,建议分散', context.t('financing_concentration_desc'),
AppColors.info, AppColors.info,
Icons.info_rounded, Icons.info_rounded,
), ),
@ -713,8 +714,8 @@ class FinancingAnalysisPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('AI 融资策略建议', style: AppTypography.h3), Text(context.t('financing_ai_title'), style: AppTypography.h3),
Text('基于您的经营数据智能分析', style: AppTypography.caption), Text(context.t('financing_ai_subtitle'), style: AppTypography.caption),
], ],
), ),
), ),
@ -748,7 +749,7 @@ class FinancingAnalysisPage extends StatelessWidget {
borderRadius: BorderRadius.circular(AppSpacing.radiusFull), borderRadius: BorderRadius.circular(AppSpacing.radiusFull),
), ),
child: Text( child: Text(
'$priority优先', priority == '' ? context.t('financing_priority_high') : context.t('financing_priority_medium'),
style: AppTypography.caption.copyWith( style: AppTypography.caption.copyWith(
color: priorityColor, color: priorityColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -772,7 +773,7 @@ class FinancingAnalysisPage extends StatelessWidget {
Navigator.pushNamed(context, AppRouter.aiAgent); Navigator.pushNamed(context, AppRouter.aiAgent);
}, },
icon: const Icon(Icons.auto_awesome_rounded, size: 18), icon: const Icon(Icons.auto_awesome_rounded, size: 18),
label: const Text('获取详细融资方案'), label: Text(context.t('financing_get_detail_plan')),
), ),
), ),
], ],

View File

@ -2,6 +2,7 @@ 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 '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -15,19 +16,19 @@ class ReconciliationPage extends StatefulWidget {
} }
class _ReconciliationPageState extends State<ReconciliationPage> { class _ReconciliationPageState extends State<ReconciliationPage> {
String _selectedPeriod = ''; String _selectedPeriod = 'reconciliation_period_month';
final _periods = ['', '', '', '季度']; final _periodKeys = ['reconciliation_period_day', 'reconciliation_period_week', 'reconciliation_period_month', 'reconciliation_period_quarter'];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('对账与结算'), title: Text(context.t('reconciliation_title')),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.download_rounded), icon: const Icon(Icons.download_rounded),
onPressed: () => _showExportDialog(context), onPressed: () => _showExportDialog(context),
tooltip: '导出报表', tooltip: context.t('reconciliation_export_tooltip'),
), ),
], ],
), ),
@ -76,11 +77,11 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
borderRadius: BorderRadius.circular(AppSpacing.radiusSm), borderRadius: BorderRadius.circular(AppSpacing.radiusSm),
), ),
child: Row( child: Row(
children: _periods.map((p) { children: _periodKeys.map((key) {
final isSelected = _selectedPeriod == p; final isSelected = _selectedPeriod == key;
return Expanded( return Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () => setState(() => _selectedPeriod = p), onTap: () => setState(() => _selectedPeriod = key),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -90,7 +91,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
), ),
child: Center( child: Center(
child: Text( child: Text(
p, context.t(key),
style: AppTypography.labelMedium.copyWith( style: AppTypography.labelMedium.copyWith(
color: isSelected ? AppColors.primary : AppColors.textSecondary, color: isSelected ? AppColors.primary : AppColors.textSecondary,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
@ -111,10 +112,10 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
Widget _buildSummaryCards() { Widget _buildSummaryCards() {
final summaries = [ final summaries = [
('应结金额', '\$132,490', AppColors.primary, Icons.account_balance_rounded), (context.t('reconciliation_expected'), '\$132,490', AppColors.primary, Icons.account_balance_rounded),
('已结金额', '\$118,200', AppColors.success, Icons.check_circle_rounded), (context.t('reconciliation_settled'), '\$118,200', AppColors.success, Icons.check_circle_rounded),
('待结金额', '\$12,180', AppColors.warning, Icons.schedule_rounded), (context.t('reconciliation_pending'), '\$12,180', AppColors.warning, Icons.schedule_rounded),
('差异金额', '\$2,110', AppColors.error, Icons.error_outline_rounded), (context.t('reconciliation_discrepancy_amount'), '\$2,110', AppColors.error, Icons.error_outline_rounded),
]; ];
return GridView.builder( return GridView.builder(
@ -190,8 +191,8 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('自动对账', style: AppTypography.labelMedium), Text(context.t('reconciliation_auto_title'), style: AppTypography.labelMedium),
Text('上次运行: 今天 06:00', style: AppTypography.caption), Text(context.t('reconciliation_auto_last_run'), style: AppTypography.caption),
], ],
), ),
), ),
@ -202,7 +203,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
borderRadius: BorderRadius.circular(AppSpacing.radiusFull), borderRadius: BorderRadius.circular(AppSpacing.radiusFull),
), ),
child: Text( child: Text(
'运行中', context.t('reconciliation_auto_running'),
style: AppTypography.caption.copyWith( style: AppTypography.caption.copyWith(
color: AppColors.success, color: AppColors.success,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -215,7 +216,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('匹配率', style: AppTypography.bodySmall), Text(context.t('reconciliation_match_rate'), style: AppTypography.bodySmall),
Text( Text(
'$matchRate%', '$matchRate%',
style: AppTypography.labelMedium.copyWith(color: AppColors.success), style: AppTypography.labelMedium.copyWith(color: AppColors.success),
@ -236,9 +237,9 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildMiniStat('已匹配', '4,832', AppColors.success), _buildMiniStat(context.t('reconciliation_matched'), '4,832', AppColors.success),
_buildMiniStat('待核查', '158', AppColors.warning), _buildMiniStat(context.t('reconciliation_to_check'), '158', AppColors.warning),
_buildMiniStat('有差异', '12', AppColors.error), _buildMiniStat(context.t('reconciliation_has_diff'), '12', AppColors.error),
], ],
), ),
], ],
@ -267,7 +268,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('对账明细', style: AppTypography.h3), Text(context.t('reconciliation_detail_title'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -289,11 +290,11 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
), ),
child: Row( child: Row(
children: [ children: [
Expanded(flex: 2, child: Text('期间', style: AppTypography.labelSmall)), Expanded(flex: 2, child: Text(context.t('reconciliation_col_period'), style: AppTypography.labelSmall)),
Expanded(flex: 2, child: Text('应结', style: AppTypography.labelSmall)), Expanded(flex: 2, child: Text(context.t('reconciliation_col_expected'), style: AppTypography.labelSmall)),
Expanded(flex: 2, child: Text('实结', style: AppTypography.labelSmall)), Expanded(flex: 2, child: Text(context.t('reconciliation_col_actual'), style: AppTypography.labelSmall)),
Expanded(flex: 2, child: Text('差异', style: AppTypography.labelSmall)), Expanded(flex: 2, child: Text(context.t('reconciliation_col_diff'), style: AppTypography.labelSmall)),
Expanded(flex: 2, child: Text('状态', style: AppTypography.labelSmall)), Expanded(flex: 2, child: Text(context.t('reconciliation_col_status'), style: AppTypography.labelSmall)),
], ],
), ),
), ),
@ -386,7 +387,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('差异调查', style: AppTypography.h3), Text(context.t('reconciliation_discrepancy_title'), style: AppTypography.h3),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -394,7 +395,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
borderRadius: BorderRadius.circular(AppSpacing.radiusFull), borderRadius: BorderRadius.circular(AppSpacing.radiusFull),
), ),
child: Text( child: Text(
'3 项待处理', '3 ${context.t('reconciliation_pending_items')}',
style: AppTypography.caption.copyWith( style: AppTypography.caption.copyWith(
color: AppColors.error, color: AppColors.error,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -454,7 +455,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'差异金额: $amount', '${context.t('reconciliation_discrepancy_amount_label')}: $amount',
style: AppTypography.bodySmall.copyWith(color: AppColors.error), style: AppTypography.bodySmall.copyWith(color: AppColors.error),
), ),
Text(date, style: AppTypography.caption), Text(date, style: AppTypography.caption),
@ -483,7 +484,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('导出报表', style: AppTypography.h3), Text(context.t('reconciliation_export_title'), style: AppTypography.h3),
const SizedBox(height: 14), const SizedBox(height: 14),
Row( Row(
children: [ children: [
@ -493,7 +494,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
// TODO: Export reconciliation report as PDF // TODO: Export reconciliation report as PDF
}, },
icon: const Icon(Icons.picture_as_pdf_rounded, size: 18), icon: const Icon(Icons.picture_as_pdf_rounded, size: 18),
label: const Text('导出 PDF'), label: Text(context.t('finance_export_pdf')),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
minimumSize: const Size(0, 48), minimumSize: const Size(0, 48),
foregroundColor: AppColors.error, foregroundColor: AppColors.error,
@ -508,7 +509,7 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
// TODO: Export reconciliation report as Excel // TODO: Export reconciliation report as Excel
}, },
icon: const Icon(Icons.table_chart_rounded, size: 18), icon: const Icon(Icons.table_chart_rounded, size: 18),
label: const Text('导出 Excel'), label: Text(context.t('finance_export_excel')),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
minimumSize: const Size(0, 48), minimumSize: const Size(0, 48),
foregroundColor: AppColors.success, foregroundColor: AppColors.success,
@ -531,35 +532,35 @@ class _ReconciliationPageState extends State<ReconciliationPage> {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => SimpleDialog( builder: (ctx) => SimpleDialog(
title: const Text('导出对账报表'), title: Text(context.t('reconciliation_export_dialog_title')),
children: [ children: [
SimpleDialogOption( SimpleDialogOption(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Row( child: Row(
children: [ children: [
Icon(Icons.picture_as_pdf_rounded, color: AppColors.error, size: 20), const Icon(Icons.picture_as_pdf_rounded, color: AppColors.error, size: 20),
SizedBox(width: 12), const SizedBox(width: 12),
Text('导出 PDF'), Text(context.t('finance_export_pdf')),
], ],
), ),
), ),
SimpleDialogOption( SimpleDialogOption(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Row( child: Row(
children: [ children: [
Icon(Icons.table_chart_rounded, color: AppColors.success, size: 20), const Icon(Icons.table_chart_rounded, color: AppColors.success, size: 20),
SizedBox(width: 12), const SizedBox(width: 12),
Text('导出 Excel'), Text(context.t('finance_export_excel')),
], ],
), ),
), ),
SimpleDialogOption( SimpleDialogOption(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Row( child: Row(
children: [ children: [
Icon(Icons.description_rounded, color: AppColors.info, size: 20), const Icon(Icons.description_rounded, color: AppColors.info, size: 20),
SizedBox(width: 12), const SizedBox(width: 12),
Text('导出 CSV'), Text(context.t('finance_export_csv')),
], ],
), ),
), ),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/router.dart'; import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -25,7 +26,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('企业入驻')), appBar: AppBar(title: Text(context.t('onboarding_title'))),
body: Column( body: Column(
children: [ children: [
// Step Indicator // Step Indicator
@ -49,14 +50,14 @@ class _OnboardingPageState extends State<OnboardingPage> {
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
onPressed: _goBack, onPressed: _goBack,
child: const Text('上一步'), child: Text(context.t('prev_step')),
), ),
), ),
if (_currentStep.index > 0 && _currentStep.index < 3) const SizedBox(width: 12), if (_currentStep.index > 0 && _currentStep.index < 3) const SizedBox(width: 12),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: _goNext, onPressed: _goNext,
child: Text(_currentStep.index < 2 ? '下一步' : '提交审核'), child: Text(_currentStep.index < 2 ? context.t('next') : context.t('onboarding_submit_review')),
), ),
), ),
], ],
@ -68,7 +69,12 @@ class _OnboardingPageState extends State<OnboardingPage> {
} }
Widget _buildStepIndicator() { Widget _buildStepIndicator() {
final steps = ['企业信息', '资质上传', '联系人', '审核中']; final steps = [
context.t('onboarding_step_company'),
context.t('onboarding_step_documents'),
context.t('onboarding_step_contact'),
context.t('onboarding_step_review'),
];
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row( child: Row(
@ -143,27 +149,27 @@ class _OnboardingPageState extends State<OnboardingPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('企业基本信息', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('onboarding_company_title'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('请填写真实的企业信息,用于入驻审核', style: TextStyle(color: AppColors.textSecondary)), Text(context.t('onboarding_company_subtitle'), style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 24), const SizedBox(height: 24),
TextField( TextField(
controller: _companyNameController, controller: _companyNameController,
decoration: const InputDecoration(labelText: '企业名称', hintText: '请输入企业全称'), decoration: InputDecoration(labelText: context.t('onboarding_company_name'), hintText: context.t('onboarding_company_name_hint')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _licenseController, controller: _licenseController,
decoration: const InputDecoration(labelText: '统一社会信用代码', hintText: '请输入18位信用代码'), decoration: InputDecoration(labelText: context.t('onboarding_credit_code'), hintText: context.t('onboarding_credit_code_hint')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
decoration: const InputDecoration(labelText: '企业类型'), decoration: InputDecoration(labelText: context.t('onboarding_company_type')),
items: const [ items: [
DropdownMenuItem(value: 'restaurant', child: Text('餐饮企业')), DropdownMenuItem(value: 'restaurant', child: Text(context.t('onboarding_type_restaurant'))),
DropdownMenuItem(value: 'retail', child: Text('零售企业')), DropdownMenuItem(value: 'retail', child: Text(context.t('onboarding_type_retail'))),
DropdownMenuItem(value: 'entertainment', child: Text('娱乐/文旅')), DropdownMenuItem(value: 'entertainment', child: Text(context.t('onboarding_type_entertainment'))),
DropdownMenuItem(value: 'other', child: Text('其他')), DropdownMenuItem(value: 'other', child: Text(context.t('onboarding_type_other'))),
], ],
onChanged: (_) { onChanged: (_) {
// TODO: Update selected company type // TODO: Update selected company type
@ -171,7 +177,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
decoration: const InputDecoration(labelText: '企业地址', hintText: '请输入企业注册地址'), decoration: InputDecoration(labelText: context.t('onboarding_company_address'), hintText: context.t('onboarding_company_address_hint')),
), ),
// AI合规助手 // AI合规助手
@ -183,17 +189,17 @@ class _OnboardingPageState extends State<OnboardingPage> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.primary.withValues(alpha: 0.15)), border: Border.all(color: AppColors.primary.withValues(alpha: 0.15)),
), ),
child: const Row( child: Row(
children: [ children: [
Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20), const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20),
SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('AI 合规助手', style: TextStyle(fontSize: 13, color: AppColors.primary, fontWeight: FontWeight.w600)), Text(context.t('onboarding_ai_compliance'), style: const TextStyle(fontSize: 13, color: AppColors.primary, fontWeight: FontWeight.w600)),
SizedBox(height: 2), const SizedBox(height: 2),
Text('填写信息后AI将自动检查合规要求', style: TextStyle(fontSize: 12, color: AppColors.textSecondary)), Text(context.t('onboarding_ai_compliance_desc'), style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)),
], ],
), ),
), ),
@ -208,15 +214,15 @@ class _OnboardingPageState extends State<OnboardingPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('资质文件上传', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('onboarding_doc_title'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('请上传清晰的企业资质文件', style: TextStyle(color: AppColors.textSecondary)), Text(context.t('onboarding_doc_subtitle'), style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 24), const SizedBox(height: 24),
_buildUploadArea('营业执照', Icons.business_rounded, true), _buildUploadArea(context.t('onboarding_doc_license'), Icons.business_rounded, true),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildUploadArea('法人身份证(正反面)', Icons.badge_rounded, true), _buildUploadArea(context.t('onboarding_doc_id'), Icons.badge_rounded, true),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildUploadArea('行业资质证书(可选)', Icons.verified_rounded, false), _buildUploadArea(context.t('onboarding_doc_cert'), Icons.verified_rounded, false),
], ],
); );
} }
@ -237,14 +243,14 @@ class _OnboardingPageState extends State<OnboardingPage> {
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
), ),
if (required) if (required)
const Text('*必填', style: TextStyle(fontSize: 11, color: AppColors.error)), Text(context.t('onboarding_required'), style: const TextStyle(fontSize: 11, color: AppColors.error)),
const SizedBox(height: 8), const SizedBox(height: 8),
OutlinedButton.icon( OutlinedButton.icon(
onPressed: () { onPressed: () {
// TODO: Open file picker for document upload // TODO: Open file picker for document upload
}, },
icon: const Icon(Icons.upload_rounded, size: 18), icon: const Icon(Icons.upload_rounded, size: 18),
label: const Text('点击上传'), label: Text(context.t('onboarding_upload')),
), ),
], ],
), ),
@ -255,26 +261,26 @@ class _OnboardingPageState extends State<OnboardingPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('联系人信息', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('onboarding_contact_title'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 24), const SizedBox(height: 24),
TextField( TextField(
controller: _contactController, controller: _contactController,
decoration: const InputDecoration(labelText: '联系人姓名'), decoration: InputDecoration(labelText: context.t('onboarding_contact_name')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _contactPhoneController, controller: _contactPhoneController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
decoration: const InputDecoration(labelText: '联系人手机号'), decoration: InputDecoration(labelText: context.t('onboarding_contact_phone')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(labelText: '企业邮箱'), decoration: InputDecoration(labelText: context.t('onboarding_contact_email')),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
decoration: const InputDecoration(labelText: '职位/角色'), decoration: InputDecoration(labelText: context.t('onboarding_contact_role')),
), ),
], ],
); );
@ -296,13 +302,13 @@ class _OnboardingPageState extends State<OnboardingPage> {
child: const Icon(Icons.hourglass_bottom_rounded, color: AppColors.warning, size: 40), child: const Icon(Icons.hourglass_bottom_rounded, color: AppColors.warning, size: 40),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
const Text('审核中', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700)), Text(context.t('onboarding_review_title'), style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('您的入驻申请已提交预计1-3个工作日内完成审核', style: TextStyle(color: AppColors.textSecondary)), Text(context.t('onboarding_review_desc'), style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 32), const SizedBox(height: 32),
OutlinedButton( OutlinedButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: const Text('返回登录'), child: Text(context.t('onboarding_back_login')),
), ),
], ],
), ),
@ -325,13 +331,13 @@ class _OnboardingPageState extends State<OnboardingPage> {
child: const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 40), child: const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 40),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
const Text('审核通过', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700)), Text(context.t('onboarding_approved_title'), style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('恭喜!您已获得白银级初始额度', style: TextStyle(color: AppColors.textSecondary)), Text(context.t('onboarding_approved_desc'), style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 32), const SizedBox(height: 32),
ElevatedButton( ElevatedButton(
onPressed: () => Navigator.pushReplacementNamed(context, AppRouter.main), onPressed: () => Navigator.pushReplacementNamed(context, AppRouter.main),
child: const Text('进入控制台'), child: Text(context.t('onboarding_enter_console')),
), ),
], ],
), ),
@ -354,13 +360,13 @@ class _OnboardingPageState extends State<OnboardingPage> {
child: const Icon(Icons.cancel_rounded, color: AppColors.error, size: 40), child: const Icon(Icons.cancel_rounded, color: AppColors.error, size: 40),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
const Text('审核未通过', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700)), Text(context.t('onboarding_rejected_title'), style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('原因:资质文件不清晰,请重新上传', style: TextStyle(color: AppColors.textSecondary)), Text(context.t('onboarding_rejected_desc'), style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 32), const SizedBox(height: 32),
ElevatedButton( ElevatedButton(
onPressed: () => setState(() => _currentStep = OnboardingStep.documents), onPressed: () => setState(() => _currentStep = OnboardingStep.documents),
child: const Text('重新提交'), child: Text(context.t('onboarding_resubmit')),
), ),
], ],
), ),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -13,11 +14,11 @@ class RedemptionPage extends StatelessWidget {
length: 2, length: 2,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('核销管理'), title: Text(context.t('redemption_title')),
bottom: const TabBar( bottom: TabBar(
tabs: [ tabs: [
Tab(text: '扫码核销'), Tab(text: context.t('redemption_tab_scan')),
Tab(text: '核销记录'), Tab(text: context.t('redemption_tab_history')),
], ],
), ),
), ),
@ -62,7 +63,7 @@ class _ScanRedeemTab extends StatelessWidget {
child: const Icon(Icons.qr_code_scanner_rounded, color: AppColors.primary, size: 64), child: const Icon(Icons.qr_code_scanner_rounded, color: AppColors.primary, size: 64),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('将券码对准扫描框', style: TextStyle(color: Colors.white70, fontSize: 14)), Text(context.t('redemption_scan_hint'), style: const TextStyle(color: Colors.white70, fontSize: 14)),
], ],
), ),
), ),
@ -74,9 +75,9 @@ class _ScanRedeemTab extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '手动输入券码', hintText: context.t('redemption_manual_hint'),
prefixIcon: Icon(Icons.keyboard_rounded), prefixIcon: const Icon(Icons.keyboard_rounded),
), ),
), ),
), ),
@ -85,7 +86,7 @@ class _ScanRedeemTab extends StatelessWidget {
height: 52, height: 52,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => _showRedeemConfirm(context), onPressed: () => _showRedeemConfirm(context),
child: const Text('核销'), child: Text(context.t('redemption_redeem')),
), ),
), ),
], ],
@ -96,7 +97,7 @@ class _ScanRedeemTab extends StatelessWidget {
OutlinedButton.icon( OutlinedButton.icon(
onPressed: () => _showBatchRedeem(context), onPressed: () => _showBatchRedeem(context),
icon: const Icon(Icons.list_alt_rounded), icon: const Icon(Icons.list_alt_rounded),
label: const Text('批量核销'), label: Text(context.t('redemption_batch')),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
minimumSize: const Size(double.infinity, 48), minimumSize: const Size(double.infinity, 48),
), ),
@ -114,14 +115,14 @@ class _ScanRedeemTab extends StatelessWidget {
child: const Column( child: const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('今日核销', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), Text(context.t('redemption_today_title'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_StatItem(label: '核销次数', value: '45'), _StatItem(label: context.t('redemption_today_count'), value: '45'),
_StatItem(label: '核销金额', value: '\$1,125'), _StatItem(label: context.t('redemption_today_amount'), value: '\$1,125'),
_StatItem(label: '门店数', value: '3'), _StatItem(label: context.t('redemption_today_stores'), value: '3'),
], ],
), ),
], ],
@ -142,7 +143,7 @@ class _ScanRedeemTab extends StatelessWidget {
children: [ children: [
const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 56), const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 56),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('核销确认', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('redemption_confirm_title'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('¥25 星巴克礼品卡', style: TextStyle(color: AppColors.textSecondary)), const Text('¥25 星巴克礼品卡', style: TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 24), const SizedBox(height: 24),
@ -150,7 +151,7 @@ class _ScanRedeemTab extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Text('确认核销'), child: Text(context.t('redemption_confirm_button')),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@ -172,17 +173,17 @@ class _ScanRedeemTab extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('批量核销', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)), Text(context.t('redemption_batch'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('输入多个券码,每行一个', style: TextStyle(color: AppColors.textSecondary)), Text(context.t('redemption_batch_desc'), style: const TextStyle(color: AppColors.textSecondary)),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: TextField( child: TextField(
maxLines: null, maxLines: null,
expands: true, expands: true,
textAlignVertical: TextAlignVertical.top, textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '粘贴券码列表...', hintText: context.t('redemption_batch_hint'),
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
), ),
@ -192,7 +193,7 @@ class _ScanRedeemTab extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Text('批量核销'), child: Text(context.t('redemption_batch')),
), ),
), ),
], ],

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/router.dart'; import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -11,7 +12,7 @@ class SettingsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('我的')), appBar: AppBar(title: Text(context.t('settings_title'))),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
@ -24,44 +25,44 @@ class SettingsPage extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
// Menu Groups // Menu Groups
_buildMenuGroup('企业管理', [ _buildMenuGroup(context.t('settings_group_company'), [
_MenuItem('企业信息', Icons.business_rounded, () { _MenuItem(context.t('settings_company_info'), Icons.business_rounded, () {
// TODO: Navigate to company info page when available // TODO: Navigate to company info page when available
}), }),
_MenuItem('门店管理', Icons.store_rounded, () { _MenuItem(context.t('settings_store_mgmt'), Icons.store_rounded, () {
Navigator.pushNamed(context, AppRouter.storeManagement); Navigator.pushNamed(context, AppRouter.storeManagement);
}), }),
_MenuItem('员工管理', Icons.people_rounded, () { _MenuItem(context.t('settings_employee_mgmt'), Icons.people_rounded, () {
Navigator.pushNamed(context, AppRouter.storeManagement); Navigator.pushNamed(context, AppRouter.storeManagement);
}), }),
_MenuItem('权限设置', Icons.admin_panel_settings_rounded, () { _MenuItem(context.t('settings_permissions'), Icons.admin_panel_settings_rounded, () {
// TODO: Navigate to permissions page when available // TODO: Navigate to permissions page when available
}), }),
]), ]),
_buildMenuGroup('服务支持', [ _buildMenuGroup(context.t('settings_group_support'), [
_MenuItem('AI 助手', Icons.auto_awesome_rounded, () { _MenuItem(context.t('settings_ai_assistant'), Icons.auto_awesome_rounded, () {
Navigator.pushNamed(context, AppRouter.aiAgent); Navigator.pushNamed(context, AppRouter.aiAgent);
}), }),
_MenuItem('专属客服', Icons.headset_mic_rounded, () { _MenuItem(context.t('settings_customer_service'), Icons.headset_mic_rounded, () {
// TODO: Navigate to customer service page when available // TODO: Navigate to customer service page when available
}), }),
_MenuItem('帮助中心', Icons.help_outline_rounded, () { _MenuItem(context.t('settings_help_center'), Icons.help_outline_rounded, () {
// TODO: Navigate to help center page when available // TODO: Navigate to help center page when available
}), }),
_MenuItem('意见反馈', Icons.feedback_rounded, () { _MenuItem(context.t('settings_feedback'), Icons.feedback_rounded, () {
// TODO: Navigate to feedback page when available // TODO: Navigate to feedback page when available
}), }),
]), ]),
_buildMenuGroup('安全与账号', [ _buildMenuGroup(context.t('settings_group_security'), [
_MenuItem('修改密码', Icons.lock_outline_rounded, () { _MenuItem(context.t('settings_change_password'), Icons.lock_outline_rounded, () {
// TODO: Navigate to change password page when available // TODO: Navigate to change password page when available
}), }),
_MenuItem('操作日志', Icons.history_rounded, () { _MenuItem(context.t('settings_operation_log'), Icons.history_rounded, () {
// TODO: Navigate to operation log page when available // TODO: Navigate to operation log page when available
}), }),
_MenuItem('关于 Genex', Icons.info_outline_rounded, () { _MenuItem(context.t('settings_about'), Icons.info_outline_rounded, () {
// TODO: Navigate to about page when available // TODO: Navigate to about page when available
}), }),
]), ]),
@ -79,7 +80,7 @@ class SettingsPage extends StatelessWidget {
foregroundColor: AppColors.error, foregroundColor: AppColors.error,
side: const BorderSide(color: AppColors.error), side: const BorderSide(color: AppColors.error),
), ),
child: const Text('退出登录'), child: Text(context.t('settings_logout')),
), ),
), ),
), ),
@ -105,13 +106,13 @@ class SettingsPage extends StatelessWidget {
child: const Icon(Icons.storefront_rounded, color: Colors.white, size: 28), child: const Icon(Icons.storefront_rounded, color: Colors.white, size: 28),
), ),
const SizedBox(width: 14), const SizedBox(width: 14),
const Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Starbucks China', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), const Text('Starbucks China', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)),
SizedBox(height: 4), const SizedBox(height: 4),
Text('管理员:张经理', style: TextStyle(fontSize: 13, color: AppColors.textSecondary)), Text('${context.t('settings_admin')}:张经理', style: const TextStyle(fontSize: 13, color: AppColors.textSecondary)),
], ],
), ),
), ),
@ -135,12 +136,12 @@ class SettingsPage extends StatelessWidget {
children: [ children: [
const Icon(Icons.star_rounded, color: AppColors.tierGold, size: 28), const Icon(Icons.star_rounded, color: AppColors.tierGold, size: 28),
const SizedBox(width: 12), const SizedBox(width: 12),
const Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('黄金发行方', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: AppColors.tierGold)), Text(context.t('settings_gold_issuer'), style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: AppColors.tierGold)),
Text('手续费率 1.2% · 高级数据分析', style: TextStyle(fontSize: 12, color: AppColors.textSecondary)), const Text('手续费率 1.2% · 高级数据分析', style: TextStyle(fontSize: 12, color: AppColors.textSecondary)),
], ],
), ),
), ),
@ -148,7 +149,7 @@ class SettingsPage extends StatelessWidget {
onPressed: () { onPressed: () {
Navigator.pushNamed(context, AppRouter.credit); Navigator.pushNamed(context, AppRouter.credit);
}, },
child: const Text('升级', style: TextStyle(color: AppColors.tierGold)), child: Text(context.t('settings_upgrade'), style: const TextStyle(color: AppColors.tierGold)),
), ),
], ],
), ),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_colors.dart';
import '../../../../app/i18n/app_localizations.dart';
/// ///
/// ///
@ -14,11 +15,11 @@ class StoreManagementPage extends StatelessWidget {
length: 2, length: 2,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('门店管理'), title: Text(context.t('store_title')),
bottom: const TabBar( bottom: TabBar(
tabs: [ tabs: [
Tab(text: '门店列表'), Tab(text: context.t('store_tab_list')),
Tab(text: '员工管理'), Tab(text: context.t('store_tab_employees')),
], ],
), ),
), ),
@ -96,7 +97,7 @@ class _StoreListTab extends StatelessWidget {
borderRadius: BorderRadius.circular(999), borderRadius: BorderRadius.circular(999),
), ),
child: Text( child: Text(
store.isActive ? '营业中' : '休息中', store.isActive ? context.t('store_status_open') : context.t('store_status_closed'),
style: TextStyle(fontSize: 10, color: store.isActive ? AppColors.success : AppColors.textTertiary), style: TextStyle(fontSize: 10, color: store.isActive ? AppColors.success : AppColors.textTertiary),
), ),
), ),
@ -107,7 +108,7 @@ class _StoreListTab extends StatelessWidget {
], ],
), ),
), ),
Text('${store.staffCount}', style: const TextStyle(fontSize: 12, color: AppColors.textTertiary)), Text('${store.staffCount}${context.t('store_people_unit')}', style: const TextStyle(fontSize: 12, color: AppColors.textTertiary)),
const SizedBox(width: 4), const SizedBox(width: 4),
const Icon(Icons.chevron_right_rounded, size: 18, color: AppColors.textTertiary), const Icon(Icons.chevron_right_rounded, size: 18, color: AppColors.textTertiary),
], ],
@ -169,8 +170,8 @@ class _EmployeeListTab extends StatelessWidget {
subtitle: Text('$role · $store', style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)), subtitle: Text('$role · $store', style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)),
trailing: PopupMenuButton<String>( trailing: PopupMenuButton<String>(
itemBuilder: (ctx) => [ itemBuilder: (ctx) => [
const PopupMenuItem(value: 'edit', child: Text('编辑')), PopupMenuItem(value: 'edit', child: Text(context.t('store_emp_edit'))),
const PopupMenuItem(value: 'delete', child: Text('移除')), PopupMenuItem(value: 'delete', child: Text(context.t('store_emp_remove'))),
], ],
), ),
); );

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'app/theme/app_theme.dart'; import 'app/theme/app_theme.dart';
import 'app/router.dart'; import 'app/router.dart';
import 'app/i18n/app_localizations.dart';
void main() { void main() {
runApp(const GenexIssuerApp()); runApp(const GenexIssuerApp());
@ -19,6 +21,18 @@ class GenexIssuerApp extends StatelessWidget {
title: 'Genex Issuer Console', title: 'Genex Issuer Console',
theme: AppTheme.light, theme: AppTheme.light,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
locale: const Locale('zh', 'CN'),
supportedLocales: const [
Locale('zh', 'CN'),
Locale('en', 'US'),
Locale('ja', 'JP'),
],
localizationsDelegates: const [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
initialRoute: AppRouter.splash, initialRoute: AppRouter.splash,
onGenerateRoute: AppRouter.generateRoute, onGenerateRoute: AppRouter.generateRoute,
); );

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../app/theme/app_colors.dart'; import '../../app/theme/app_colors.dart';
import '../../app/i18n/app_localizations.dart';
/// AI建议卡片 /// AI建议卡片
/// ///
@ -42,7 +43,7 @@ class AiSuggestionCard extends StatelessWidget {
child: const Icon(Icons.auto_awesome_rounded, color: Colors.white, size: 14), child: const Icon(Icons.auto_awesome_rounded, color: Colors.white, size: 14),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text('AI 建议', style: TextStyle(fontSize: 13, color: AppColors.primary, fontWeight: FontWeight.w600)), Text(context.t('ai_suggestion_label'), style: const TextStyle(fontSize: 13, color: AppColors.primary, fontWeight: FontWeight.w600)),
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@ -56,7 +57,7 @@ class AiSuggestionCard extends StatelessWidget {
children: [ children: [
TextButton( TextButton(
onPressed: onDismiss, onPressed: onDismiss,
child: const Text('忽略', style: TextStyle(fontSize: 13)), child: Text(context.t('ai_suggestion_dismiss'), style: const TextStyle(fontSize: 13)),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
@ -65,7 +66,7 @@ class AiSuggestionCard extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
minimumSize: Size.zero, minimumSize: Size.zero,
), ),
child: const Text('采纳', style: TextStyle(fontSize: 13)), child: Text(context.t('ai_suggestion_accept'), style: const TextStyle(fontSize: 13)),
), ),
], ],
), ),

View File

@ -9,6 +9,8 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations:
sdk: flutter
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import Link from 'next/link'; import Link from 'next/link';
import { t } from '@/i18n/locales';
/** /**
* D. Web管理前端 - * D. Web管理前端 -
@ -20,72 +21,72 @@ interface NavItem {
} }
const navItems: NavItem[] = [ const navItems: NavItem[] = [
{ key: 'dashboard', icon: '📊', label: '运营总览' }, { key: 'dashboard', icon: '📊', label: t('nav_dashboard') },
{ {
key: 'issuers', icon: '🏢', label: '发行方管理', key: 'issuers', icon: '🏢', label: t('nav_issuers'),
children: [ children: [
{ key: 'issuers/review', icon: '', label: '入驻审核' }, { key: 'issuers/review', icon: '', label: t('nav_issuers_review') },
{ key: 'issuers/list', icon: '', label: '发行方列表' }, { key: 'issuers/list', icon: '', label: t('nav_issuers_list') },
{ key: 'issuers/coupons', icon: '', label: '券审核' }, { key: 'issuers/coupons', icon: '', label: t('nav_issuers_coupons') },
], ],
}, },
{ {
key: 'users', icon: '👤', label: '用户管理', key: 'users', icon: '👤', label: t('nav_users'),
children: [ children: [
{ key: 'users/list', icon: '', label: '用户列表' }, { key: 'users/list', icon: '', label: t('nav_users_list') },
{ key: 'users/kyc', icon: '', label: 'KYC审核', badge: 5 }, { key: 'users/kyc', icon: '', label: t('nav_users_kyc'), badge: 5 },
], ],
}, },
{ {
key: 'trading', icon: '💱', label: '交易监控', key: 'trading', icon: '💱', label: t('nav_trading'),
children: [ children: [
{ key: 'trading/realtime', icon: '', label: '实时交易流' }, { key: 'trading/realtime', icon: '', label: t('nav_trading_realtime') },
{ key: 'trading/stats', icon: '', label: '交易统计' }, { key: 'trading/stats', icon: '', label: t('nav_trading_stats') },
{ key: 'trading/orders', icon: '', label: '订单管理' }, { key: 'trading/orders', icon: '', label: t('nav_trading_orders') },
], ],
}, },
{ {
key: 'risk', icon: '🛡️', label: '风控中心', key: 'risk', icon: '🛡️', label: t('nav_risk'),
children: [ children: [
{ key: 'risk/dashboard', icon: '', label: '风险仪表盘', badge: 3 }, { key: 'risk/dashboard', icon: '', label: t('nav_risk_dashboard'), badge: 3 },
{ key: 'risk/suspicious', icon: '', label: '可疑交易' }, { key: 'risk/suspicious', icon: '', label: t('nav_risk_suspicious') },
{ key: 'risk/blacklist', icon: '', label: '黑名单管理' }, { key: 'risk/blacklist', icon: '', label: t('nav_risk_blacklist') },
{ key: 'risk/ofac', icon: '', label: 'OFAC筛查日志' }, { key: 'risk/ofac', icon: '', label: t('nav_risk_ofac') },
], ],
}, },
{ {
key: 'compliance', icon: '📋', label: '合规报表', key: 'compliance', icon: '📋', label: t('nav_compliance'),
children: [ children: [
{ key: 'compliance/sar', icon: '', label: 'SAR管理' }, { key: 'compliance/sar', icon: '', label: t('nav_compliance_sar') },
{ key: 'compliance/ctr', icon: '', label: 'CTR管理' }, { key: 'compliance/ctr', icon: '', label: t('nav_compliance_ctr') },
{ key: 'compliance/audit', icon: '', label: '审计日志' }, { key: 'compliance/audit', icon: '', label: t('nav_compliance_audit') },
{ key: 'compliance/reports', icon: '', label: '监管报表' }, { key: 'compliance/reports', icon: '', label: t('nav_compliance_reports') },
], ],
}, },
{ {
key: 'system', icon: '⚙️', label: '系统管理', key: 'system', icon: '⚙️', label: t('nav_system'),
children: [ children: [
{ key: 'system/admins', icon: '', label: '管理员账号' }, { key: 'system/admins', icon: '', label: t('nav_system_admins') },
{ key: 'system/config', icon: '', label: '系统配置' }, { key: 'system/config', icon: '', label: t('nav_system_config') },
{ key: 'system/contracts', icon: '', label: '合约管理' }, { key: 'system/contracts', icon: '', label: t('nav_system_contracts') },
{ key: 'system/monitor', icon: '', label: '系统监控' }, { key: 'system/monitor', icon: '', label: t('nav_system_monitor') },
], ],
}, },
{ key: 'disputes', icon: '⚖️', label: '争议处理', badge: 8 }, { key: 'disputes', icon: '⚖️', label: t('nav_disputes'), badge: 8 },
{ key: 'coupons', icon: '🎫', label: '券管理' }, { key: 'coupons', icon: '🎫', label: t('nav_coupons_mgmt') },
{ key: 'finance', icon: '💰', label: '财务管理' }, { key: 'finance', icon: '💰', label: t('nav_finance') },
{ key: 'chain', icon: '⛓️', label: '链上监控' }, { key: 'chain', icon: '⛓️', label: t('nav_chain') },
{ key: 'reports', icon: '📈', label: '报表中心' }, { key: 'reports', icon: '📈', label: t('nav_reports') },
{ key: 'merchant', icon: '🏪', label: '商户核销' }, { key: 'merchant', icon: '🏪', label: t('nav_merchant') },
{ key: 'agent', icon: '🤖', label: '代理面板' }, { key: 'agent', icon: '🤖', label: t('nav_agent') },
{ key: 'insurance', icon: '🛡️', label: '保险管理' }, { key: 'insurance', icon: '🛡️', label: t('nav_insurance') },
{ {
key: 'analytics', icon: '📊', label: '数据分析', key: 'analytics', icon: '📊', label: t('nav_analytics'),
children: [ children: [
{ key: 'analytics/users', icon: '', label: '用户分析' }, { key: 'analytics/users', icon: '', label: t('nav_analytics_users') },
{ key: 'analytics/coupons', icon: '', label: '券分析' }, { key: 'analytics/coupons', icon: '', label: t('nav_analytics_coupons') },
{ key: 'analytics/market-maker', icon: '', label: '做市商分析' }, { key: 'analytics/market-maker', icon: '', label: t('nav_analytics_market_maker') },
{ key: 'analytics/consumer-protection', icon: '', label: '消费者保护' }, { key: 'analytics/consumer-protection', icon: '', label: t('nav_analytics_consumer_protection') },
], ],
}, },
]; ];
@ -266,7 +267,7 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
}} }}
> >
{collapsed ? '→' : '← 收起'} {collapsed ? '→' : `${t('collapse')}`}
</button> </button>
</aside> </aside>
@ -284,7 +285,7 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
}}> }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<input <input
placeholder="搜索用户/订单/交易..." placeholder={t('header_search_placeholder')}
style={{ style={{
width: 320, width: 320,
height: 36, height: 36,
@ -311,7 +312,7 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}}> }}>
AI {t('header_ai_assistant')}
</button> </button>
{/* Notifications */} {/* Notifications */}
<button style={{ <button style={{

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D7. AI Agent管理面板 - AI Agent运营监控 * D7. AI Agent管理面板 - AI Agent运营监控
@ -7,10 +8,10 @@ import React from 'react';
*/ */
const agentStats = [ const agentStats = [
{ label: '今日会话数', value: '3,456', change: '+18%', color: 'var(--color-primary)' }, { label: t('agent_today_sessions'), value: '3,456', change: '+18%', color: 'var(--color-primary)' },
{ label: '平均响应时间', value: '1.2s', change: '-0.3s', color: 'var(--color-success)' }, { label: t('agent_avg_response'), value: '1.2s', change: '-0.3s', color: 'var(--color-success)' },
{ label: '用户满意度', value: '94.5%', change: '+2.1%', color: 'var(--color-info)' }, { label: t('agent_satisfaction'), value: '94.5%', change: '+2.1%', color: 'var(--color-info)' },
{ label: '人工接管率', value: '3.2%', change: '-0.5%', color: 'var(--color-warning)' }, { label: t('agent_human_takeover'), value: '3.2%', change: '-0.5%', color: 'var(--color-warning)' },
]; ];
const topQuestions = [ const topQuestions = [
@ -33,7 +34,7 @@ const agentModules = [
export const AgentPanelPage: React.FC = () => { export const AgentPanelPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>AI Agent </h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>{t('agent_title')}</h1>
{/* Stats */} {/* Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
@ -52,7 +53,7 @@ export const AgentPanelPage: React.FC = () => {
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
{/* Top Questions */} {/* Top Questions */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}> Top 5</h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('agent_top_questions')}</h2>
{topQuestions.map((q, i) => ( {topQuestions.map((q, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<span style={{ <span style={{
@ -67,14 +68,14 @@ export const AgentPanelPage: React.FC = () => {
background: 'var(--color-primary-surface)', color: 'var(--color-primary)', background: 'var(--color-primary-surface)', color: 'var(--color-primary)',
}}>{q.category}</span> }}>{q.category}</span>
</div> </div>
<span style={{ font: 'var(--text-label)', color: 'var(--color-primary)' }}>{q.count}</span> <span style={{ font: 'var(--text-label)', color: 'var(--color-primary)' }}>{q.count}{t('agent_unit_times')}</span>
</div> </div>
))} ))}
</div> </div>
{/* Agent Modules */} {/* Agent Modules */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>Agent </h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('agent_modules')}</h2>
{agentModules.map((m, i) => ( {agentModules.map((m, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
@ -84,13 +85,13 @@ export const AgentPanelPage: React.FC = () => {
padding: '1px 6px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600, padding: '1px 6px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600,
background: m.status === 'active' ? 'var(--color-success-light)' : 'var(--color-warning-light)', background: m.status === 'active' ? 'var(--color-success-light)' : 'var(--color-warning-light)',
color: m.status === 'active' ? 'var(--color-success)' : 'var(--color-warning)', color: m.status === 'active' ? 'var(--color-success)' : 'var(--color-warning)',
}}>{m.status === 'active' ? '运行中' : 'Beta'}</span> }}>{m.status === 'active' ? t('agent_running') : 'Beta'}</span>
</div> </div>
<div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>{m.desc}</div> <div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>{m.desc}</div>
</div> </div>
<div style={{ textAlign: 'right' }}> <div style={{ textAlign: 'right' }}>
<div style={{ font: 'var(--text-label)', color: 'var(--color-success)' }}>{m.accuracy}</div> <div style={{ font: 'var(--text-label)', color: 'var(--color-success)' }}>{m.accuracy}</div>
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}></div> <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{t('accuracy')}</div>
</div> </div>
</div> </div>
))} ))}

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* *
@ -7,17 +8,17 @@ import React from 'react';
*/ */
const stats = [ const stats = [
{ label: '投诉总数', value: '234', change: '-5.2%', trend: 'down' as const, color: 'var(--color-error)' }, { label: t('cp_total_complaints'), value: '234', change: '-5.2%', trend: 'down' as const, color: 'var(--color-error)' },
{ label: '已解决', value: '198', change: '+12.3%', trend: 'up' as const, color: 'var(--color-success)' }, { label: t('cp_resolved'), value: '198', change: '+12.3%', trend: 'up' as const, color: 'var(--color-success)' },
{ label: '处理中', value: '28', change: '-8.1%', trend: 'down' as const, color: 'var(--color-warning)' }, { label: t('cp_processing'), value: '28', change: '-8.1%', trend: 'down' as const, color: 'var(--color-warning)' },
{ label: '平均解决时间', value: '2.3天', change: '-0.4天', trend: 'down' as const, color: 'var(--color-info)' }, { label: t('cp_avg_resolution_time'), value: '2.3d', change: '-0.4d', trend: 'down' as const, color: 'var(--color-info)' },
]; ];
const complaintCategories = [ const complaintCategories = [
{ name: '核销失败', count: 82, percent: 35, color: 'var(--color-error)' }, { name: t('cp_cat_redeem_fail'), count: 82, percent: 35, color: 'var(--color-error)' },
{ name: '退款纠纷', count: 68, percent: 29, color: 'var(--color-warning)' }, { name: t('cp_cat_refund_dispute'), count: 68, percent: 29, color: 'var(--color-warning)' },
{ name: '虚假券', count: 49, percent: 21, color: 'var(--color-primary)' }, { name: t('cp_cat_fake_coupon'), count: 49, percent: 21, color: 'var(--color-primary)' },
{ name: '其他', count: 35, percent: 15, color: 'var(--color-gray-400)' }, { name: t('cp_cat_other'), count: 35, percent: 15, color: 'var(--color-gray-400)' },
]; ];
const csatTrend = [ const csatTrend = [
@ -30,45 +31,45 @@ const csatTrend = [
]; ];
const recentComplaints = [ const recentComplaints = [
{ id: 'CMP-0234', severity: '高' as const, category: '虚假券', title: '品牌方否认发行该优惠券', status: '处理中' as const, assignee: '张明', created: '2026-02-10' }, { id: 'CMP-0234', severity: t('severity_high'), category: t('cp_cat_fake_coupon'), title: 'Brand denies issuing this coupon', status: t('cp_processing'), assignee: 'Zhang Ming', created: '2026-02-10' },
{ id: 'CMP-0233', severity: '高' as const, category: '退款纠纷', title: '核销后商家拒绝提供服务', status: '处理中' as const, assignee: '李华', created: '2026-02-10' }, { id: 'CMP-0233', severity: t('severity_high'), category: t('cp_cat_refund_dispute'), title: 'Merchant refused service after redemption', status: t('cp_processing'), assignee: 'Li Hua', created: '2026-02-10' },
{ id: 'CMP-0232', severity: '中' as const, category: '核销失败', title: '二维码扫描无反应,门店系统故障', status: '已解决' as const, assignee: '王芳', created: '2026-02-09' }, { id: 'CMP-0232', severity: t('severity_medium'), category: t('cp_cat_redeem_fail'), title: 'QR code scan no response - store system failure', status: t('cp_resolved'), assignee: 'Wang Fang', created: '2026-02-09' },
{ id: 'CMP-0231', severity: '低' as const, category: '其他', title: '券面信息与实际服务不符', status: '已解决' as const, assignee: '赵丽', created: '2026-02-09' }, { id: 'CMP-0231', severity: t('severity_low'), category: t('cp_cat_other'), title: 'Coupon info mismatch with actual service', status: t('cp_resolved'), assignee: 'Zhao Li', created: '2026-02-09' },
{ id: 'CMP-0230', severity: '高' as const, category: '退款纠纷', title: '过期券退款被拒,用户称未收到提醒', status: '处理中' as const, assignee: '张明', created: '2026-02-09' }, { id: 'CMP-0230', severity: t('severity_high'), category: t('cp_cat_refund_dispute'), title: 'Expired coupon refund denied, user claims no reminder', status: t('cp_processing'), assignee: 'Zhang Ming', created: '2026-02-09' },
{ id: 'CMP-0229', severity: '中' as const, category: '核销失败', title: '跨区域核销失败,券面未标注限制', status: '已解决' as const, assignee: '李华', created: '2026-02-08' }, { id: 'CMP-0229', severity: t('severity_medium'), category: t('cp_cat_redeem_fail'), title: 'Cross-region redemption failed, no restriction noted', status: t('cp_resolved'), assignee: 'Li Hua', created: '2026-02-08' },
{ id: 'CMP-0228', severity: '低' as const, category: '其他', title: '赠送的券对方未收到', status: '已解决' as const, assignee: '王芳', created: '2026-02-08' }, { id: 'CMP-0228', severity: t('severity_low'), category: t('cp_cat_other'), title: 'Gifted coupon not received by recipient', status: t('cp_resolved'), assignee: 'Wang Fang', created: '2026-02-08' },
{ id: 'CMP-0227', severity: '中' as const, category: '虚假券', title: '折扣力度与宣传不符', status: '处理中' as const, assignee: '赵丽', created: '2026-02-07' }, { id: 'CMP-0227', severity: t('severity_medium'), category: t('cp_cat_fake_coupon'), title: 'Discount amount differs from advertisement', status: t('cp_processing'), assignee: 'Zhao Li', created: '2026-02-07' },
]; ];
const severityConfig: Record<string, { bg: string; color: string }> = { const severityConfig: Record<string, { bg: string; color: string }> = {
'高': { bg: 'var(--color-error-light)', color: 'var(--color-error)' }, [t('severity_high')]: { bg: 'var(--color-error-light)', color: 'var(--color-error)' },
'中': { bg: 'var(--color-warning-light)', color: 'var(--color-warning)' }, [t('severity_medium')]: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)' },
'低': { bg: 'var(--color-info-light)', color: 'var(--color-info)' }, [t('severity_low')]: { bg: 'var(--color-info-light)', color: 'var(--color-info)' },
}; };
const statusConfig: Record<string, { bg: string; color: string }> = { const statusConfig: Record<string, { bg: string; color: string }> = {
'处理中': { bg: 'var(--color-warning-light)', color: 'var(--color-warning)' }, [t('cp_processing')]: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)' },
'已解决': { bg: 'var(--color-success-light)', color: 'var(--color-success)' }, [t('cp_resolved')]: { bg: 'var(--color-success-light)', color: 'var(--color-success)' },
'已关闭': { bg: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }, [t('completed')]: { bg: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' },
}; };
const nonCompliantIssuers = [ const nonCompliantIssuers = [
{ rank: 1, issuer: '快乐生活电商', violations: 12, refundRate: '45%', avgDelay: '5.2天', riskLevel: '高' as const }, { rank: 1, issuer: 'Happy Life E-commerce', violations: 12, refundRate: '45%', avgDelay: '5.2d', riskLevel: t('severity_high') },
{ rank: 2, issuer: '优选旅游服务', violations: 9, refundRate: '52%', avgDelay: '4.8天', riskLevel: '高' as const }, { rank: 2, issuer: 'Premium Travel Services', violations: 9, refundRate: '52%', avgDelay: '4.8d', riskLevel: t('severity_high') },
{ rank: 3, issuer: '星辰数码官方', violations: 7, refundRate: '61%', avgDelay: '3.5天', riskLevel: '中' as const }, { rank: 3, issuer: 'Star Digital Official', violations: 7, refundRate: '61%', avgDelay: '3.5d', riskLevel: t('severity_medium') },
{ rank: 4, issuer: '美味餐饮集团', violations: 5, refundRate: '68%', avgDelay: '2.9天', riskLevel: '中' as const }, { rank: 4, issuer: 'Gourmet Restaurant Group', violations: 5, refundRate: '68%', avgDelay: '2.9d', riskLevel: t('severity_medium') },
{ rank: 5, issuer: '悦享娱乐传媒', violations: 4, refundRate: '72%', avgDelay: '2.1天', riskLevel: '低' as const }, { rank: 5, issuer: 'Joy Entertainment Media', violations: 4, refundRate: '72%', avgDelay: '2.1d', riskLevel: t('severity_low') },
]; ];
export const ConsumerProtectionPage: React.FC = () => { export const ConsumerProtectionPage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}>{t('cp_title')}</h1>
<button style={{ <button style={{
padding: '8px 16px', border: 'none', borderRadius: 'var(--radius-sm)', padding: '8px 16px', border: 'none', borderRadius: 'var(--radius-sm)',
background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label)', background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label)',
}}></button> }}>{t('export_report')}</button>
</div> </div>
{/* Stats Grid */} {/* Stats Grid */}
@ -88,7 +89,7 @@ export const ConsumerProtectionPage: React.FC = () => {
</div> </div>
<div style={{ <div style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: (stat.label === '投诉总数' || stat.label === '处理中' || stat.label === '平均解决时间') color: (stat.label === t('cp_total_complaints') || stat.label === t('cp_processing') || stat.label === t('cp_avg_resolution_time'))
? (stat.trend === 'down' ? 'var(--color-success)' : 'var(--color-error)') ? (stat.trend === 'down' ? 'var(--color-success)' : 'var(--color-error)')
: (stat.trend === 'up' ? 'var(--color-success)' : 'var(--color-error)'), : (stat.trend === 'up' ? 'var(--color-success)' : 'var(--color-error)'),
marginTop: 4, marginTop: 4,
@ -108,13 +109,13 @@ export const ConsumerProtectionPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('cp_complaint_categories')}</div>
{complaintCategories.map(cat => ( {complaintCategories.map(cat => (
<div key={cat.name} style={{ marginBottom: 14 }}> <div key={cat.name} style={{ marginBottom: 14 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
<span style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-primary)' }}>{cat.name}</span> <span style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-primary)' }}>{cat.name}</span>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}> <span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>
{cat.count} ({cat.percent}%) {cat.count} {t('cp_unit_cases')} ({cat.percent}%)
</span> </span>
</div> </div>
<div style={{ <div style={{
@ -144,7 +145,7 @@ export const ConsumerProtectionPage: React.FC = () => {
padding: 20, padding: 20,
flex: 1, flex: 1,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 12 }}> (CSAT)</div> <div style={{ font: 'var(--text-h3)', marginBottom: 12 }}>{t('cp_csat')}</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 12 }}> <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 12 }}>
<span style={{ font: 'var(--text-h1)', color: 'var(--color-success)' }}>4.5</span> <span style={{ font: 'var(--text-h1)', color: 'var(--color-success)' }}>4.5</span>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>/5.0</span> <span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>/5.0</span>
@ -182,7 +183,7 @@ export const ConsumerProtectionPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 12 }}>使</div> <div style={{ font: 'var(--text-h3)', marginBottom: 12 }}>{t('cp_protection_fund')}</div>
<div style={{ <div style={{
height: 100, height: 100,
background: 'var(--color-gray-50)', background: 'var(--color-gray-50)',
@ -207,9 +208,9 @@ export const ConsumerProtectionPage: React.FC = () => {
marginBottom: 24, marginBottom: 24,
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ font: 'var(--text-h3)' }}></span> <span style={{ font: 'var(--text-h3)' }}>{t('cp_recent_complaints')}</span>
<input <input
placeholder="搜索投诉..." placeholder={t('cp_search_complaints')}
style={{ style={{
width: 240, height: 32, width: 240, height: 32,
border: '1px solid var(--color-border)', border: '1px solid var(--color-border)',
@ -222,7 +223,7 @@ export const ConsumerProtectionPage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['编号', '严重度', '分类', '描述', '状态', '负责人', '日期'].map(h => ( {[t('cp_th_id'), t('cp_th_severity'), t('cp_th_category'), t('cp_th_description'), t('cp_th_status'), t('cp_th_assignee'), t('cp_th_date')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -278,12 +279,12 @@ export const ConsumerProtectionPage: React.FC = () => {
overflow: 'hidden', overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}>
退 - Top 5 {t('cp_refund_compliance')}
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['排名', '发行方', '违规次数', '退款通过率', '平均处理延迟', '风险等级', '操作'].map(h => ( {[t('cp_th_rank'), t('cp_th_issuer'), t('cp_th_violations'), t('cp_th_refund_rate'), t('cp_th_avg_delay'), t('cp_th_risk_level'), t('actions')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -335,8 +336,8 @@ export const ConsumerProtectionPage: React.FC = () => {
}}>{row.riskLevel}</span> }}>{row.riskLevel}</span>
</td> </td>
<td style={{ padding: '10px 14px', display: 'flex', gap: 4 }}> <td style={{ padding: '10px 14px', display: 'flex', gap: 4 }}>
<button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)' }}></button> <button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)' }}>{t('investigate')}</button>
<button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-warning)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}></button> <button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-warning)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}>{t('warning_label')}</button>
</td> </td>
</tr> </tr>
); );

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* *
@ -7,10 +8,10 @@ import React from 'react';
*/ */
const stats = [ const stats = [
{ label: '券总量', value: '45,230', change: '+6.5%', trend: 'up' as const, color: 'var(--color-primary)' }, { label: t('ca_total_coupons'), value: '45,230', change: '+6.5%', trend: 'up' as const, color: 'var(--color-primary)' },
{ label: '活跃券', value: '32,100', change: '+3.2%', trend: 'up' as const, color: 'var(--color-success)' }, { label: t('ca_active_coupons'), value: '32,100', change: '+3.2%', trend: 'up' as const, color: 'var(--color-success)' },
{ label: '已核销', value: '8,450', change: '+12.1%', trend: 'up' as const, color: 'var(--color-info)' }, { label: t('ca_redeemed'), value: '8,450', change: '+12.1%', trend: 'up' as const, color: 'var(--color-info)' },
{ label: '即将过期', value: '2,340', change: '+8.7%', trend: 'up' as const, color: 'var(--color-warning)' }, { label: t('ca_expiring_soon'), value: '2,340', change: '+8.7%', trend: 'up' as const, color: 'var(--color-warning)' },
]; ];
const categoryDistribution = [ const categoryDistribution = [
@ -44,19 +45,19 @@ const breakageTrend = [
]; ];
const secondaryMarket = [ const secondaryMarket = [
{ metric: '挂牌率', value: '23.5%', change: '+1.2%', trend: 'up' as const }, { metric: 'Listing Rate', value: '23.5%', change: '+1.2%', trend: 'up' as const },
{ metric: '平均加价率', value: '8.3%', change: '-0.5%', trend: 'down' as const }, { metric: 'Avg Markup', value: '8.3%', change: '-0.5%', trend: 'down' as const },
{ metric: '日均交易量', value: '1,230', change: '+15.2%', trend: 'up' as const }, { metric: 'Daily Volume', value: '1,230', change: '+15.2%', trend: 'up' as const },
{ metric: '日均交易额', value: '$98,400', change: '+11.8%', trend: 'up' as const }, { metric: 'Daily Amount', value: '$98,400', change: '+11.8%', trend: 'up' as const },
{ metric: '平均成交时间', value: '4.2h', change: '-8.3%', trend: 'down' as const }, { metric: 'Avg Fill Time', value: '4.2h', change: '-8.3%', trend: 'down' as const },
{ metric: '撤单率', value: '12.1%', change: '+0.8%', trend: 'up' as const }, { metric: 'Cancel Rate', value: '12.1%', change: '+0.8%', trend: 'up' as const },
]; ];
export const CouponAnalyticsPage: React.FC = () => { export const CouponAnalyticsPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>
{t('ca_title')}
</h1> </h1>
{/* Stats Grid */} {/* Stats Grid */}
@ -94,7 +95,7 @@ export const CouponAnalyticsPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('ca_category_distribution')}</div>
{categoryDistribution.map(cat => ( {categoryDistribution.map(cat => (
<div key={cat.name} style={{ marginBottom: 14 }}> <div key={cat.name} style={{ marginBottom: 14 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
@ -127,7 +128,7 @@ export const CouponAnalyticsPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('ca_face_value_distribution')}</div>
<div style={{ <div style={{
height: 260, height: 260,
background: 'var(--color-gray-50)', background: 'var(--color-gray-50)',
@ -151,12 +152,12 @@ export const CouponAnalyticsPage: React.FC = () => {
marginBottom: 24, marginBottom: 24,
}}> }}>
<div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}>
Top 10 {t('ca_top_selling')}
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['排名', '品牌', '券名称', '销量', '收入', '评分'].map(h => ( {[t('ca_th_rank'), t('ca_th_brand'), t('ca_th_coupon_name'), t('ca_th_sales'), t('ca_th_revenue'), t('ca_th_rating')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -205,7 +206,7 @@ export const CouponAnalyticsPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>Breakage趋势 ()</div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('ca_breakage_trend')}</div>
<div style={{ <div style={{
height: 160, height: 160,
background: 'var(--color-gray-50)', background: 'var(--color-gray-50)',
@ -240,7 +241,7 @@ export const CouponAnalyticsPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('ca_secondary_market')}</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
{secondaryMarket.map(item => ( {secondaryMarket.map(item => (
<div key={item.metric} style={{ <div key={item.metric} style={{

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* *
@ -7,10 +8,10 @@ import React from 'react';
*/ */
const stats = [ const stats = [
{ label: '活跃做市商', value: '12', change: '+2', trend: 'up' as const, color: 'var(--color-primary)' }, { label: t('mm_active_makers'), value: '12', change: '+2', trend: 'up' as const, color: 'var(--color-primary)' },
{ label: '总流动性', value: '$5.2M', change: '+8.3%', trend: 'up' as const, color: 'var(--color-success)' }, { label: t('mm_total_liquidity'), value: '$5.2M', change: '+8.3%', trend: 'up' as const, color: 'var(--color-success)' },
{ label: '日均交易量', value: '$320K', change: '+12.5%', trend: 'up' as const, color: 'var(--color-info)' }, { label: t('mm_daily_volume'), value: '$320K', change: '+12.5%', trend: 'up' as const, color: 'var(--color-info)' },
{ label: '平均价差', value: '1.8%', change: '-0.3%', trend: 'down' as const, color: 'var(--color-warning)' }, { label: t('mm_avg_spread'), value: '1.8%', change: '-0.3%', trend: 'down' as const, color: 'var(--color-warning)' },
]; ];
const marketMakers = [ const marketMakers = [
@ -25,9 +26,9 @@ const marketMakers = [
]; ];
const statusConfig: Record<string, { bg: string; color: string; label: string }> = { const statusConfig: Record<string, { bg: string; color: string; label: string }> = {
active: { bg: 'var(--color-success-light)', color: 'var(--color-success)', label: '活跃' }, active: { bg: 'var(--color-success-light)', color: 'var(--color-success)', label: t('mm_status_active') },
paused: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)', label: '暂停' }, paused: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)', label: t('mm_status_paused') },
suspended: { bg: 'var(--color-error-light)', color: 'var(--color-error)', label: '停用' }, suspended: { bg: 'var(--color-error-light)', color: 'var(--color-error)', label: t('mm_status_suspended') },
}; };
const liquidityPools = [ const liquidityPools = [
@ -55,20 +56,20 @@ const riskAlerts = [
]; ];
const severityConfig: Record<string, { bg: string; color: string; label: string }> = { const severityConfig: Record<string, { bg: string; color: string; label: string }> = {
high: { bg: 'var(--color-error-light)', color: 'var(--color-error)', label: '高' }, high: { bg: 'var(--color-error-light)', color: 'var(--color-error)', label: t('severity_high') },
medium: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)', label: '中' }, medium: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)', label: t('severity_medium') },
low: { bg: 'var(--color-info-light)', color: 'var(--color-info)', label: '低' }, low: { bg: 'var(--color-info-light)', color: 'var(--color-info)', label: t('severity_low') },
}; };
export const MarketMakerPage: React.FC = () => { export const MarketMakerPage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}>{t('mm_title')}</h1>
<button style={{ <button style={{
padding: '8px 16px', border: 'none', borderRadius: 'var(--radius-sm)', padding: '8px 16px', border: 'none', borderRadius: 'var(--radius-sm)',
background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label)', background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label)',
}}></button> }}>{t('mm_add_new')}</button>
</div> </div>
{/* Stats Grid */} {/* Stats Grid */}
@ -89,8 +90,8 @@ export const MarketMakerPage: React.FC = () => {
<div style={{ <div style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: stat.trend === 'up' color: stat.trend === 'up'
? (stat.label === '平均价差' ? 'var(--color-error)' : 'var(--color-success)') ? (stat.label === t('mm_avg_spread') ? 'var(--color-error)' : 'var(--color-success)')
: (stat.label === '平均价差' ? 'var(--color-success)' : 'var(--color-error)'), : (stat.label === t('mm_avg_spread') ? 'var(--color-success)' : 'var(--color-error)'),
marginTop: 4, marginTop: 4,
}}> }}>
{stat.change} {stat.change}
@ -108,12 +109,12 @@ export const MarketMakerPage: React.FC = () => {
marginBottom: 24, marginBottom: 24,
}}> }}>
<div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}>
{t('market_maker_list')}
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['做市商', '状态', 'TVL', '价差', '日交易量', 'P&L', '操作'].map(h => ( {[t('mm_th_name'), t('mm_th_status'), 'TVL', t('market_maker_spread'), t('mm_th_daily_volume'), t('market_maker_pnl'), t('actions')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -147,12 +148,12 @@ export const MarketMakerPage: React.FC = () => {
color: mm.pnl.startsWith('+') ? 'var(--color-success)' : 'var(--color-error)', color: mm.pnl.startsWith('+') ? 'var(--color-success)' : 'var(--color-error)',
}}>{mm.pnl}</td> }}>{mm.pnl}</td>
<td style={{ padding: '10px 14px', display: 'flex', gap: 4 }}> <td style={{ padding: '10px 14px', display: 'flex', gap: 4 }}>
<button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)' }}></button> <button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)' }}>{t('details')}</button>
{mm.status === 'active' && ( {mm.status === 'active' && (
<button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-warning)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}></button> <button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-warning)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}>{t('suspend')}</button>
)} )}
{mm.status === 'paused' && ( {mm.status === 'paused' && (
<button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-success)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}></button> <button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-success)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}>{t('resume')}</button>
)} )}
</td> </td>
</tr> </tr>
@ -171,14 +172,14 @@ export const MarketMakerPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('mm_liquidity_pools')}</div>
{liquidityPools.map(pool => ( {liquidityPools.map(pool => (
<div key={pool.category} style={{ marginBottom: 14 }}> <div key={pool.category} style={{ marginBottom: 14 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
<span style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-primary)' }}> <span style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-primary)' }}>
{pool.category} {pool.category}
<span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginLeft: 8 }}> <span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginLeft: 8 }}>
{pool.makers} {pool.makers} {t('mm_unit_makers')}
</span> </span>
</span> </span>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}> <span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>
@ -209,7 +210,7 @@ export const MarketMakerPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>簿</div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('mm_order_book_depth')}</div>
<div style={{ <div style={{
height: 260, height: 260,
background: 'var(--color-gray-50)', background: 'var(--color-gray-50)',
@ -233,7 +234,7 @@ export const MarketMakerPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('mm_health_indicators')}</div>
{healthIndicators.map(ind => ( {healthIndicators.map(ind => (
<div key={ind.name} style={{ <div key={ind.name} style={{
display: 'flex', display: 'flex',
@ -270,7 +271,7 @@ export const MarketMakerPage: React.FC = () => {
padding: 20, padding: 20,
}}> }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<div style={{ font: 'var(--text-h3)' }}></div> <div style={{ font: 'var(--text-h3)' }}>{t('mm_risk_alerts')}</div>
<span style={{ <span style={{
padding: '2px 8px', padding: '2px 8px',
borderRadius: 'var(--radius-full)', borderRadius: 'var(--radius-full)',
@ -278,7 +279,7 @@ export const MarketMakerPage: React.FC = () => {
color: 'var(--color-error)', color: 'var(--color-error)',
font: 'var(--text-caption)', font: 'var(--text-caption)',
}}> }}>
{riskAlerts.filter(a => a.severity === 'high').length} {riskAlerts.filter(a => a.severity === 'high').length} {t('mm_high_risk')}
</span> </span>
</div> </div>
{riskAlerts.map((alert, i) => { {riskAlerts.map((alert, i) => {

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* *
@ -7,10 +8,10 @@ import React from 'react';
*/ */
const stats = [ const stats = [
{ label: '总用户数', value: '128,456', change: '+3.2%', trend: 'up' as const, color: 'var(--color-primary)' }, { label: t('ua_total_users'), value: '128,456', change: '+3.2%', trend: 'up' as const, color: 'var(--color-primary)' },
{ label: 'DAU', value: '12,340', change: '+5.8%', trend: 'up' as const, color: 'var(--color-success)' }, { label: 'DAU', value: '12,340', change: '+5.8%', trend: 'up' as const, color: 'var(--color-success)' },
{ label: 'MAU', value: '45,678', change: '+2.1%', trend: 'up' as const, color: 'var(--color-info)' }, { label: 'MAU', value: '45,678', change: '+2.1%', trend: 'up' as const, color: 'var(--color-info)' },
{ label: '新增用户/周', value: '1,234', change: '-1.4%', trend: 'down' as const, color: 'var(--color-warning)' }, { label: t('ua_new_users_week'), value: '1,234', change: '-1.4%', trend: 'down' as const, color: 'var(--color-warning)' },
]; ];
const kycDistribution = [ const kycDistribution = [
@ -42,17 +43,17 @@ const cohortRetention = [
]; ];
const userSegments = [ const userSegments = [
{ name: '高频交易', count: '8,456', percent: 6.6, color: 'var(--color-primary)' }, { name: t('ua_segment_high_freq'), count: '8,456', percent: 6.6, color: 'var(--color-primary)' },
{ name: '偶尔购买', count: '34,230', percent: 26.6, color: 'var(--color-success)' }, { name: t('ua_segment_occasional'), count: '34,230', percent: 26.6, color: 'var(--color-success)' },
{ name: '仅浏览', count: '52,890', percent: 41.2, color: 'var(--color-warning)' }, { name: t('ua_segment_browse'), count: '52,890', percent: 41.2, color: 'var(--color-warning)' },
{ name: '流失用户', count: '32,880', percent: 25.6, color: 'var(--color-error)' }, { name: t('ua_segment_churned'), count: '32,880', percent: 25.6, color: 'var(--color-error)' },
]; ];
export const UserAnalyticsPage: React.FC = () => { export const UserAnalyticsPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>
{t('ua_title')}
</h1> </h1>
{/* Stats Grid */} {/* Stats Grid */}
@ -90,7 +91,7 @@ export const UserAnalyticsPage: React.FC = () => {
marginBottom: 24, marginBottom: 24,
}}> }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<span style={{ font: 'var(--text-h3)' }}></span> <span style={{ font: 'var(--text-h3)' }}>{t('ua_growth_trend')}</span>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
{['7D', '30D', '90D', '1Y'].map(p => ( {['7D', '30D', '90D', '1Y'].map(p => (
<button key={p} style={{ <button key={p} style={{
@ -127,7 +128,7 @@ export const UserAnalyticsPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>KYC等级分布</div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('ua_kyc_distribution')}</div>
{kycDistribution.map(item => ( {kycDistribution.map(item => (
<div key={item.level} style={{ marginBottom: 16 }}> <div key={item.level} style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
@ -161,12 +162,12 @@ export const UserAnalyticsPage: React.FC = () => {
overflow: 'hidden', overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}>
(Top 10) {t('ua_geo_distribution')}
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['排名', '地区', '用户数', '占比'].map(h => ( {[t('ua_th_rank'), t('ua_th_region'), t('ua_th_users'), t('ua_th_percent')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -200,12 +201,12 @@ export const UserAnalyticsPage: React.FC = () => {
overflow: 'hidden', overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}>
{t('ua_retention_matrix')}
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['注册周', 'Week 0', 'Week 1', 'Week 2', 'Week 3', 'Week 4'].map(h => ( {[t('ua_th_cohort'), 'Week 0', 'Week 1', 'Week 2', 'Week 3', 'Week 4'].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -247,7 +248,7 @@ export const UserAnalyticsPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('ua_user_segments')}</div>
{userSegments.map(seg => ( {userSegments.map(seg => (
<div key={seg.name} style={{ <div key={seg.name} style={{
display: 'flex', display: 'flex',
@ -264,7 +265,7 @@ export const UserAnalyticsPage: React.FC = () => {
}} /> }} />
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<div style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-primary)' }}>{seg.name}</div> <div style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-primary)' }}>{seg.name}</div>
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{seg.count} </div> <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{seg.count} {t('ua_unit_people')}</div>
</div> </div>
<div style={{ textAlign: 'right' }}> <div style={{ textAlign: 'right' }}>
<div style={{ font: 'var(--text-h3)', color: seg.color }}>{seg.percent}%</div> <div style={{ font: 'var(--text-h3)', color: seg.color }}>{seg.percent}%</div>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D4. - * D4. -
@ -31,7 +32,7 @@ const eventColors: Record<string, string> = {
export const ChainMonitorPage: React.FC = () => { export const ChainMonitorPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>{t('chain_title')}</h1>
{/* Contract Status */} {/* Contract Status */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
@ -55,7 +56,7 @@ export const ChainMonitorPage: React.FC = () => {
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
{/* Chain Events */} {/* Chain Events */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('chain_recent_events')}</h2>
{recentEvents.map((e, i) => ( {recentEvents.map((e, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<div style={{ <div style={{
@ -75,12 +76,12 @@ export const ChainMonitorPage: React.FC = () => {
{/* Gas Monitor */} {/* Gas Monitor */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>Gas费监控</h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('chain_gas_monitor')}</h2>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12, marginBottom: 16 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12, marginBottom: 16 }}>
{[ {[
{ label: '当前Gas', value: '12 gwei', color: 'var(--color-success)' }, { label: t('chain_current_gas'), value: '12 gwei', color: 'var(--color-success)' },
{ label: '今日均值', value: '18 gwei', color: 'var(--color-info)' }, { label: t('chain_today_avg'), value: '18 gwei', color: 'var(--color-info)' },
{ label: '今日Gas支出', value: '$1,234', color: 'var(--color-warning)' }, { label: t('chain_today_gas_spend'), value: '$1,234', color: 'var(--color-warning)' },
].map(g => ( ].map(g => (
<div key={g.label} style={{ textAlign: 'center', padding: 12, background: 'var(--color-gray-50)', borderRadius: 'var(--radius-sm)' }}> <div key={g.label} style={{ textAlign: 'center', padding: 12, background: 'var(--color-gray-50)', borderRadius: 'var(--radius-sm)' }}>
<div style={{ font: 'var(--text-h3)', color: g.color }}>{g.value}</div> <div style={{ font: 'var(--text-h3)', color: g.color }}>{g.value}</div>

View File

@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { t } from '@/i18n/locales';
/** /**
* D6. * D6.
@ -14,7 +15,7 @@ export const CompliancePage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)' }}></h1> <h1 style={{ font: 'var(--text-h1)' }}>{t('compliance_title')}</h1>
<button style={{ <button style={{
padding: '8px 16px', padding: '8px 16px',
border: 'none', border: 'none',
@ -24,27 +25,27 @@ export const CompliancePage: React.FC = () => {
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}}> }}>
AI {t('compliance_ai_generate')}
</button> </button>
</div> </div>
{/* Tabs */} {/* Tabs */}
<div style={{ display: 'flex', gap: 4, marginBottom: 20, borderBottom: '1px solid var(--color-border-light)', paddingBottom: 0 }}> <div style={{ display: 'flex', gap: 4, marginBottom: 20, borderBottom: '1px solid var(--color-border-light)', paddingBottom: 0 }}>
{[ {[
{ key: 'sar', label: 'SAR管理', badge: 3 }, { key: 'sar', label: t('compliance_tab_sar'), badge: 3 },
{ key: 'ctr', label: 'CTR管理', badge: 0 }, { key: 'ctr', label: t('compliance_tab_ctr'), badge: 0 },
{ key: 'audit', label: '审计日志', badge: 0 }, { key: 'audit', label: t('compliance_tab_audit'), badge: 0 },
{ key: 'reports', label: '监管报表', badge: 0 }, { key: 'reports', label: t('compliance_tab_reports'), badge: 0 },
].map(t => ( ].map(tab => (
<button <button
key={t.key} key={tab.key}
onClick={() => setActiveTab(t.key as typeof activeTab)} onClick={() => setActiveTab(tab.key as typeof activeTab)}
style={{ style={{
padding: '10px 16px', padding: '10px 16px',
border: 'none', border: 'none',
borderBottom: activeTab === t.key ? '2px solid var(--color-primary)' : '2px solid transparent', borderBottom: activeTab === tab.key ? '2px solid var(--color-primary)' : '2px solid transparent',
background: 'none', background: 'none',
color: activeTab === t.key ? 'var(--color-primary)' : 'var(--color-text-tertiary)', color: activeTab === tab.key ? 'var(--color-primary)' : 'var(--color-text-tertiary)',
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label)', font: 'var(--text-label)',
display: 'flex', display: 'flex',
@ -52,15 +53,15 @@ export const CompliancePage: React.FC = () => {
gap: 6, gap: 6,
}} }}
> >
{t.label} {tab.label}
{t.badge > 0 && ( {tab.badge > 0 && (
<span style={{ <span style={{
padding: '1px 6px', padding: '1px 6px',
borderRadius: 'var(--radius-full)', borderRadius: 'var(--radius-full)',
background: 'var(--color-error)', background: 'var(--color-error)',
color: 'white', color: 'white',
fontSize: 10, fontSize: 10,
}}>{t.badge}</span> }}>{tab.badge}</span>
)} )}
</button> </button>
))} ))}
@ -77,16 +78,16 @@ export const CompliancePage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['SAR编号', '相关交易', '涉及用户', '金额', '风险类型', '状态', '创建时间', '操作'].map(h => ( {[t('compliance_sar_id'), t('compliance_sar_related_txn'), t('compliance_sar_user'), t('compliance_sar_amount'), t('compliance_sar_risk_type'), t('compliance_sar_status'), t('compliance_sar_created'), t('actions')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{[ {[
{ id: 'SAR-2026-001', txn: 'TXN-8901', user: 'U-045', amount: '$4,560', type: '高频交易', status: '待提交' }, { id: 'SAR-2026-001', txn: 'TXN-8901', user: 'U-045', amount: '$4,560', type: t('risk_type_high_freq'), status: t('compliance_sar_pending') },
{ id: 'SAR-2026-002', txn: 'TXN-8900', user: 'U-078', amount: '$8,900', type: '大额异常', status: '已提交' }, { id: 'SAR-2026-002', txn: 'TXN-8900', user: 'U-078', amount: '$8,900', type: t('risk_type_large_single'), status: t('compliance_sar_submitted') },
{ id: 'SAR-2026-003', txn: 'TXN-8850', user: 'U-012', amount: '$12,000', type: '结构化交易', status: '已提交' }, { id: 'SAR-2026-003', txn: 'TXN-8850', user: 'U-012', amount: '$12,000', type: t('risk_type_related_account'), status: t('compliance_sar_submitted') },
].map(sar => ( ].map(sar => (
<tr key={sar.id} style={{ borderBottom: '1px solid var(--color-border-light)' }}> <tr key={sar.id} style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ font: 'var(--text-body-sm)', fontFamily: 'var(--font-family-mono)', padding: '10px 14px' }}>{sar.id}</td> <td style={{ font: 'var(--text-body-sm)', fontFamily: 'var(--font-family-mono)', padding: '10px 14px' }}>{sar.id}</td>
@ -103,14 +104,14 @@ export const CompliancePage: React.FC = () => {
<td style={{ padding: '10px 14px' }}> <td style={{ padding: '10px 14px' }}>
<span style={{ <span style={{
padding: '2px 8px', borderRadius: 'var(--radius-full)', padding: '2px 8px', borderRadius: 'var(--radius-full)',
background: sar.status === '已提交' ? 'var(--color-success-light)' : 'var(--color-warning-light)', background: sar.status === t('compliance_sar_submitted') ? 'var(--color-success-light)' : 'var(--color-warning-light)',
color: sar.status === '已提交' ? 'var(--color-success)' : 'var(--color-warning)', color: sar.status === t('compliance_sar_submitted') ? 'var(--color-success)' : 'var(--color-warning)',
font: 'var(--text-caption)', font: 'var(--text-caption)',
}}>{sar.status}</span> }}>{sar.status}</span>
</td> </td>
<td style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px' }}>2026-02-{10 - parseInt(sar.id.slice(-1))}</td> <td style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px' }}>2026-02-{10 - parseInt(sar.id.slice(-1))}</td>
<td style={{ padding: '10px 14px' }}> <td style={{ padding: '10px 14px' }}>
<button style={{ padding: '4px 12px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-primary)' }}></button> <button style={{ padding: '4px 12px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-primary)' }}>{t('view')}</button>
</td> </td>
</tr> </tr>
))} ))}
@ -130,8 +131,8 @@ export const CompliancePage: React.FC = () => {
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
}}> }}>
<div style={{ fontSize: 48, marginBottom: 12 }}>📋</div> <div style={{ fontSize: 48, marginBottom: 12 }}>📋</div>
<div style={{ font: 'var(--text-h3)', color: 'var(--color-text-secondary)', marginBottom: 8 }}></div> <div style={{ font: 'var(--text-h3)', color: 'var(--color-text-secondary)', marginBottom: 8 }}>{t('compliance_ctr_title')}</div>
<div style={{ font: 'var(--text-body-sm)' }}>$10,000CTR</div> <div style={{ font: 'var(--text-body-sm)' }}>{t('compliance_ctr_desc')}</div>
</div> </div>
)} )}
@ -145,11 +146,11 @@ export const CompliancePage: React.FC = () => {
}}> }}>
<div style={{ padding: 16, borderBottom: '1px solid var(--color-border-light)', display: 'flex', gap: 12 }}> <div style={{ padding: 16, borderBottom: '1px solid var(--color-border-light)', display: 'flex', gap: 12 }}>
<input <input
placeholder="搜索操作日志..." placeholder={t('compliance_audit_search')}
style={{ flex: 1, height: 36, border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', padding: '0 12px', font: 'var(--text-body-sm)' }} style={{ flex: 1, height: 36, border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', padding: '0 12px', font: 'var(--text-body-sm)' }}
/> />
<button style={{ padding: '6px 14px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-label-sm)' }}> <button style={{ padding: '6px 14px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-label-sm)' }}>
{t('export')}
</button> </button>
</div> </div>
{Array.from({ length: 6 }, (_, i) => ( {Array.from({ length: 6 }, (_, i) => (
@ -166,7 +167,7 @@ export const CompliancePage: React.FC = () => {
background: 'var(--color-info-light)', color: 'var(--color-info)', background: 'var(--color-info-light)', color: 'var(--color-info)',
font: 'var(--text-caption)', font: 'var(--text-caption)',
}}> }}>
{['登录', '审核', '配置', '冻结', '导出', '查询'][i]} {[t('compliance_audit_action_login'), t('compliance_audit_action_review'), t('compliance_audit_action_config'), t('compliance_audit_action_freeze'), t('compliance_audit_action_export'), t('compliance_audit_action_query')][i]}
</span> </span>
<span style={{ font: 'var(--text-body-sm)', flex: 1 }}> <span style={{ font: 'var(--text-body-sm)', flex: 1 }}>
admin{i + 1} {['登录系统', '审核发行方ISS-003通过', '修改手续费率为2.5%', '冻结用户U-045', '导出月度报表', '查询OFAC筛查记录'][i]} admin{i + 1} {['登录系统', '审核发行方ISS-003通过', '修改手续费率为2.5%', '冻结用户U-045', '导出月度报表', '查询OFAC筛查记录'][i]}
@ -183,10 +184,10 @@ export const CompliancePage: React.FC = () => {
{activeTab === 'reports' && ( {activeTab === 'reports' && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16 }}>
{[ {[
{ title: '日报', desc: '每日交易汇总、异常事件', date: '2026-02-10', auto: true }, { title: t('compliance_report_daily'), desc: '每日交易汇总、异常事件', date: '2026-02-10', auto: true },
{ title: '月报', desc: '月度运营指标、合规状态', date: '2026-01-31', auto: true }, { title: t('compliance_report_monthly'), desc: '月度运营指标、合规状态', date: '2026-01-31', auto: true },
{ title: '季度SAR汇总', desc: '季度可疑活动报告汇总', date: '2025-12-31', auto: false }, { title: t('compliance_report_sar_quarterly'), desc: '季度可疑活动报告汇总', date: '2025-12-31', auto: false },
{ title: '年度合规报告', desc: '年度合规审计报告', date: '2025-12-31', auto: false }, { title: t('compliance_report_annual'), desc: '年度合规审计报告', date: '2025-12-31', auto: false },
].map(report => ( ].map(report => (
<div key={report.title} style={{ <div key={report.title} style={{
background: 'var(--color-surface)', background: 'var(--color-surface)',
@ -201,12 +202,12 @@ export const CompliancePage: React.FC = () => {
padding: '2px 8px', borderRadius: 'var(--radius-full)', padding: '2px 8px', borderRadius: 'var(--radius-full)',
background: 'var(--color-success-light)', color: 'var(--color-success)', background: 'var(--color-success-light)', color: 'var(--color-success)',
font: 'var(--text-caption)', font: 'var(--text-caption)',
}}></span> }}>{t('auto_generated')}</span>
)} )}
</div> </div>
<div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', marginBottom: 12 }}>{report.desc}</div> <div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', marginBottom: 12 }}>{report.desc}</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}> {report.date}</span> <span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{t('as_of')} {report.date}</span>
<button style={{ <button style={{
padding: '6px 14px', padding: '6px 14px',
border: '1px solid var(--color-primary)', border: '1px solid var(--color-primary)',
@ -215,7 +216,7 @@ export const CompliancePage: React.FC = () => {
color: 'var(--color-primary)', color: 'var(--color-primary)',
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}}></button> }}>{t('download')}</button>
</div> </div>
</div> </div>
))} ))}

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D8.4 IPO准备度检查清单 - * D8.4 IPO准备度检查清单 -
@ -19,11 +20,11 @@ interface CheckItem {
} }
const categories = [ const categories = [
{ key: 'legal', label: '法律合规', icon: '§', color: 'var(--color-primary)' }, { key: 'legal', label: t('ipo_cat_legal'), icon: '§', color: 'var(--color-primary)' },
{ key: 'financial', label: '财务审计', icon: '$', color: 'var(--color-success)' }, { key: 'financial', label: t('ipo_cat_financial'), icon: '$', color: 'var(--color-success)' },
{ key: 'sox', label: 'SOX合规', icon: '✓', color: 'var(--color-info)' }, { key: 'sox', label: t('ipo_cat_sox'), icon: '✓', color: 'var(--color-info)' },
{ key: 'governance', label: '公司治理', icon: '◆', color: 'var(--color-warning)' }, { key: 'governance', label: t('ipo_cat_governance'), icon: '◆', color: 'var(--color-warning)' },
{ key: 'insurance', label: '保险保障', icon: '☂', color: '#FF6B6B' }, { key: 'insurance', label: t('ipo_cat_insurance'), icon: '☂', color: '#FF6B6B' },
]; ];
const overallProgress = { const overallProgress = {
@ -79,28 +80,28 @@ const checklistItems: CheckItem[] = [
]; ];
const statusConfig: Record<string, { label: string; bg: string; fg: string }> = { const statusConfig: Record<string, { label: string; bg: string; fg: string }> = {
done: { label: '已完成', bg: 'var(--color-success-light)', fg: 'var(--color-success)' }, done: { label: t('completed'), bg: 'var(--color-success-light)', fg: 'var(--color-success)' },
progress: { label: '进行中', bg: 'var(--color-warning-light)', fg: 'var(--color-warning)' }, progress: { label: t('in_progress'), bg: 'var(--color-warning-light)', fg: 'var(--color-warning)' },
blocked: { label: '阻塞', bg: 'var(--color-error-light)', fg: 'var(--color-error)' }, blocked: { label: t('blocked'), bg: 'var(--color-error-light)', fg: 'var(--color-error)' },
pending: { label: '待开始', bg: 'var(--color-gray-100)', fg: 'var(--color-text-tertiary)' }, pending: { label: t('pending'), bg: 'var(--color-gray-100)', fg: 'var(--color-text-tertiary)' },
}; };
export const IpoReadinessPage: React.FC = () => { export const IpoReadinessPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 8 }}>IPO准备度检查清单</h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 8 }}>{t('ipo_title')}</h1>
<p style={{ font: 'var(--text-body)', color: 'var(--color-text-secondary)', marginBottom: 24 }}> <p style={{ font: 'var(--text-body)', color: 'var(--color-text-secondary)', marginBottom: 24 }}>
IPO里程碑 {t('ipo_subtitle')}
</p> </p>
{/* Summary Stats */} {/* Summary Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 16, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 16, marginBottom: 24 }}>
{[ {[
{ label: '总计检查项', value: overallProgress.total, color: 'var(--color-text-primary)' }, { label: t('ipo_total_items'), value: overallProgress.total, color: 'var(--color-text-primary)' },
{ label: '已完成', value: overallProgress.done, color: 'var(--color-success)' }, { label: t('completed'), value: overallProgress.done, color: 'var(--color-success)' },
{ label: '进行中', value: overallProgress.inProgress, color: 'var(--color-warning)' }, { label: t('in_progress'), value: overallProgress.inProgress, color: 'var(--color-warning)' },
{ label: '阻塞项', value: overallProgress.blocked, color: 'var(--color-error)' }, { label: t('blocked'), value: overallProgress.blocked, color: 'var(--color-error)' },
{ label: '待开始', value: overallProgress.pending, color: 'var(--color-text-tertiary)' }, { label: t('pending'), value: overallProgress.pending, color: 'var(--color-text-tertiary)' },
].map(s => ( ].map(s => (
<div key={s.label} style={{ <div key={s.label} style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
@ -118,7 +119,7 @@ export const IpoReadinessPage: React.FC = () => {
border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 24, border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 24,
}}> }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ font: 'var(--text-label)', color: 'var(--color-text-primary)' }}>IPO准备进度</span> <span style={{ font: 'var(--text-label)', color: 'var(--color-text-primary)' }}>{t('ipo_overall_progress')}</span>
<span style={{ font: 'var(--text-h2)', color: 'var(--color-primary)' }}>{overallProgress.percent}%</span> <span style={{ font: 'var(--text-h2)', color: 'var(--color-primary)' }}>{overallProgress.percent}%</span>
</div> </div>
<div style={{ height: 12, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden', display: 'flex' }}> <div style={{ height: 12, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden', display: 'flex' }}>
@ -128,10 +129,10 @@ export const IpoReadinessPage: React.FC = () => {
</div> </div>
<div style={{ display: 'flex', gap: 16, marginTop: 8 }}> <div style={{ display: 'flex', gap: 16, marginTop: 8 }}>
{[ {[
{ label: '已完成', color: 'var(--color-success)' }, { label: t('completed'), color: 'var(--color-success)' },
{ label: '进行中', color: 'var(--color-warning)' }, { label: t('in_progress'), color: 'var(--color-warning)' },
{ label: '阻塞', color: 'var(--color-error)' }, { label: t('blocked'), color: 'var(--color-error)' },
{ label: '待开始', color: 'var(--color-gray-200)' }, { label: t('pending'), color: 'var(--color-gray-200)' },
].map(l => ( ].map(l => (
<div key={l.label} style={{ display: 'flex', alignItems: 'center', gap: 4 }}> <div key={l.label} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<div style={{ width: 8, height: 8, borderRadius: '50%', background: l.color }} /> <div style={{ width: 8, height: 8, borderRadius: '50%', background: l.color }} />
@ -164,14 +165,14 @@ export const IpoReadinessPage: React.FC = () => {
<span style={{ font: 'var(--text-h3)', color: 'var(--color-text-primary)' }}>{cat.label}</span> <span style={{ font: 'var(--text-h3)', color: 'var(--color-text-primary)' }}>{cat.label}</span>
</div> </div>
<span style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-secondary)' }}> <span style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-secondary)' }}>
{catDone}/{items.length} {catDone}/{items.length} {t('ipo_unit_done')}
</span> </span>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr> <tr>
{['编号', '检查项', '负责方', '截止日', '状态'].map(h => ( {[t('ipo_th_id'), t('ipo_th_item'), t('ipo_th_owner'), t('ipo_th_deadline'), t('ipo_th_status')].map(h => (
<th key={h} style={{ <th key={h} style={{
padding: '6px 0', textAlign: 'left', font: 'var(--text-label-sm)', padding: '6px 0', textAlign: 'left', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', borderBottom: '1px solid var(--color-border-light)', color: 'var(--color-text-tertiary)', borderBottom: '1px solid var(--color-border-light)',
@ -190,7 +191,7 @@ export const IpoReadinessPage: React.FC = () => {
{item.note && <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginTop: 2 }}>{item.note}</div>} {item.note && <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginTop: 2 }}>{item.note}</div>}
{item.dependency && ( {item.dependency && (
<div style={{ font: 'var(--text-caption)', color: 'var(--color-info)', marginTop: 2 }}> <div style={{ font: 'var(--text-caption)', color: 'var(--color-info)', marginTop: 2 }}>
: {item.dependency} {t('ipo_dependency')}: {item.dependency}
</div> </div>
)} )}
</td> </td>
@ -219,7 +220,7 @@ export const IpoReadinessPage: React.FC = () => {
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 16, border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 16,
}}> }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>IPO时间线</h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('ipo_timeline')}</h2>
{milestones.map((m, i) => ( {milestones.map((m, i) => (
<div key={m.name} style={{ display: 'flex', gap: 12, marginBottom: i < milestones.length - 1 ? 0 : 0 }}> <div key={m.name} style={{ display: 'flex', gap: 12, marginBottom: i < milestones.length - 1 ? 0 : 0 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
@ -245,14 +246,14 @@ export const IpoReadinessPage: React.FC = () => {
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-error)', padding: 20, marginBottom: 16, border: '1px solid var(--color-error)', padding: 20, marginBottom: 16,
}}> }}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-error)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', color: 'var(--color-error)', marginBottom: 16 }}>{t('ipo_blockers')}</h2>
{checklistItems.filter(i => i.status === 'blocked').map(item => ( {checklistItems.filter(i => i.status === 'blocked').map(item => (
<div key={item.id} style={{ <div key={item.id} style={{
padding: 12, background: 'var(--color-error-light)', borderRadius: 'var(--radius-sm)', marginBottom: 8, padding: 12, background: 'var(--color-error-light)', borderRadius: 'var(--radius-sm)', marginBottom: 8,
}}> }}>
<div style={{ font: 'var(--text-label)', color: 'var(--color-error)' }}>{item.id}: {item.item}</div> <div style={{ font: 'var(--text-label)', color: 'var(--color-error)' }}>{item.id}: {item.item}</div>
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-secondary)', marginTop: 4 }}> <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-secondary)', marginTop: 4 }}>
: {item.owner} · : {item.deadline} {t('ipo_owner')}: {item.owner} · {t('ipo_deadline')}: {item.deadline}
</div> </div>
{item.note && ( {item.note && (
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-secondary)', marginTop: 2 }}>{item.note}</div> <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-secondary)', marginTop: 2 }}>{item.note}</div>
@ -266,7 +267,7 @@ export const IpoReadinessPage: React.FC = () => {
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 16, border: '1px solid var(--color-border-light)', padding: 20, marginBottom: 16,
}}> }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('ipo_category_progress')}</h2>
{categories.map(cat => { {categories.map(cat => {
const items = checklistItems.filter(i => i.category === cat.key); const items = checklistItems.filter(i => i.category === cat.key);
const catDone = items.filter(i => i.status === 'done').length; const catDone = items.filter(i => i.status === 'done').length;
@ -290,7 +291,7 @@ export const IpoReadinessPage: React.FC = () => {
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, border: '1px solid var(--color-border-light)', padding: 20,
}}> }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('ipo_key_contacts')}</h2>
{[ {[
{ role: '承销商 (Lead)', name: 'Goldman Sachs', status: '已签约' }, { role: '承销商 (Lead)', name: 'Goldman Sachs', status: '已签约' },
{ role: '审计师', name: 'Deloitte', status: '审计中' }, { role: '审计师', name: 'Deloitte', status: '审计中' },

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* License & Regulatory Permits Management - * License & Regulatory Permits Management -
@ -8,23 +9,23 @@ import React from 'react';
*/ */
const licenseStats = [ const licenseStats = [
{ label: '活跃牌照数', value: '12', color: 'var(--color-success)' }, { label: t('license_active_count'), value: '12', color: 'var(--color-success)' },
{ label: '待申请', value: '4', color: 'var(--color-info)' }, { label: t('license_pending'), value: '4', color: 'var(--color-info)' },
{ label: '即将到期', value: '2', color: 'var(--color-warning)' }, { label: t('license_expiring_soon'), value: '2', color: 'var(--color-warning)' },
{ label: '已吊销', value: '0', color: 'var(--color-error)' }, { label: t('license_revoked'), value: '0', color: 'var(--color-error)' },
]; ];
const licenses = [ const licenses = [
{ id: 'LIC-001', name: 'FinCEN MSB Registration', jurisdiction: 'Federal (US)', regBody: 'FinCEN', status: '有效', issueDate: '2024-06-01', expiryDate: '2026-06-01' }, { id: 'LIC-001', name: 'FinCEN MSB Registration', jurisdiction: 'Federal (US)', regBody: 'FinCEN', status: t('license_status_active'), issueDate: '2024-06-01', expiryDate: '2026-06-01' },
{ id: 'LIC-002', name: 'New York BitLicense', jurisdiction: 'New York', regBody: 'NYDFS', status: '有效', issueDate: '2024-09-15', expiryDate: '2026-09-15' }, { id: 'LIC-002', name: 'New York BitLicense', jurisdiction: 'New York', regBody: 'NYDFS', status: t('license_status_active'), issueDate: '2024-09-15', expiryDate: '2026-09-15' },
{ id: 'LIC-003', name: 'California MTL', jurisdiction: 'California', regBody: 'DFPI', status: '有效', issueDate: '2025-01-10', expiryDate: '2027-01-10' }, { id: 'LIC-003', name: 'California MTL', jurisdiction: 'California', regBody: 'DFPI', status: t('license_status_active'), issueDate: '2025-01-10', expiryDate: '2027-01-10' },
{ id: 'LIC-004', name: 'Texas Money Transmitter', jurisdiction: 'Texas', regBody: 'TDSML', status: '即将到期', issueDate: '2024-03-20', expiryDate: '2026-03-20' }, { id: 'LIC-004', name: 'Texas Money Transmitter', jurisdiction: 'Texas', regBody: 'TDSML', status: t('license_status_expiring'), issueDate: '2024-03-20', expiryDate: '2026-03-20' },
{ id: 'LIC-005', name: 'Florida Money Transmitter', jurisdiction: 'Florida', regBody: 'OFR', status: '有效', issueDate: '2025-04-01', expiryDate: '2027-04-01' }, { id: 'LIC-005', name: 'Florida Money Transmitter', jurisdiction: 'Florida', regBody: 'OFR', status: t('license_status_active'), issueDate: '2025-04-01', expiryDate: '2027-04-01' },
{ id: 'LIC-006', name: 'Illinois TOMA', jurisdiction: 'Illinois', regBody: 'IDFPR', status: '申请中', issueDate: '-', expiryDate: '-' }, { id: 'LIC-006', name: 'Illinois TOMA', jurisdiction: 'Illinois', regBody: 'IDFPR', status: t('license_status_applying'), issueDate: '-', expiryDate: '-' },
{ id: 'LIC-007', name: 'Washington Money Transmitter', jurisdiction: 'Washington', regBody: 'DFI', status: '有效', issueDate: '2025-02-15', expiryDate: '2027-02-15' }, { id: 'LIC-007', name: 'Washington Money Transmitter', jurisdiction: 'Washington', regBody: 'DFI', status: t('license_status_active'), issueDate: '2025-02-15', expiryDate: '2027-02-15' },
{ id: 'LIC-008', name: 'SEC Broker-Dealer Registration', jurisdiction: 'Federal (US)', regBody: 'SEC / FINRA', status: '申请中', issueDate: '-', expiryDate: '-' }, { id: 'LIC-008', name: 'SEC Broker-Dealer Registration', jurisdiction: 'Federal (US)', regBody: 'SEC / FINRA', status: t('license_status_applying'), issueDate: '-', expiryDate: '-' },
{ id: 'LIC-009', name: 'Georgia Money Transmitter', jurisdiction: 'Georgia', regBody: 'DBF', status: '待续期', issueDate: '2024-02-28', expiryDate: '2026-02-28' }, { id: 'LIC-009', name: 'Georgia Money Transmitter', jurisdiction: 'Georgia', regBody: 'DBF', status: t('license_status_renewal'), issueDate: '2024-02-28', expiryDate: '2026-02-28' },
{ id: 'LIC-010', name: 'Nevada Money Transmitter', jurisdiction: 'Nevada', regBody: 'FID', status: '有效', issueDate: '2025-06-01', expiryDate: '2027-06-01' }, { id: 'LIC-010', name: 'Nevada Money Transmitter', jurisdiction: 'Nevada', regBody: 'FID', status: t('license_status_active'), issueDate: '2025-06-01', expiryDate: '2027-06-01' },
]; ];
const regulatoryBodies = [ const regulatoryBodies = [
@ -45,15 +46,15 @@ const renewalAlerts = [
const getLicenseStatusStyle = (status: string) => { const getLicenseStatusStyle = (status: string) => {
switch (status) { switch (status) {
case '有效': case t('license_status_active'):
return { background: 'var(--color-success-light)', color: 'var(--color-success)' }; return { background: 'var(--color-success-light)', color: 'var(--color-success)' };
case '申请中': case t('license_status_applying'):
return { background: 'var(--color-info-light)', color: 'var(--color-info)' }; return { background: 'var(--color-info-light)', color: 'var(--color-info)' };
case '待续期': case t('license_status_renewal'):
return { background: 'var(--color-warning-light)', color: 'var(--color-warning)' }; return { background: 'var(--color-warning-light)', color: 'var(--color-warning)' };
case '即将到期': case t('license_status_expiring'):
return { background: 'var(--color-error-light)', color: 'var(--color-error)' }; return { background: 'var(--color-error-light)', color: 'var(--color-error)' };
case '已过期': case t('license_status_expired'):
return { background: 'var(--color-gray-100)', color: 'var(--color-error)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-error)' };
default: default:
return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' };
@ -79,7 +80,7 @@ export const LicenseManagementPage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}>{t('license_title')}</h1>
<button style={{ <button style={{
padding: '8px 16px', padding: '8px 16px',
border: 'none', border: 'none',
@ -89,7 +90,7 @@ export const LicenseManagementPage: React.FC = () => {
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}}> }}>
+ {t('license_new')}
</button> </button>
</div> </div>
@ -112,12 +113,12 @@ export const LicenseManagementPage: React.FC = () => {
border: '1px solid var(--color-border-light)', overflow: 'hidden', marginBottom: 24, border: '1px solid var(--color-border-light)', overflow: 'hidden', marginBottom: 24,
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}></h2> <h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>{t('license_list')}</h2>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['编号', '牌照名称', '司法管辖区', '监管机构', '签发日期', '到期日期', '状态', '操作'].map(h => ( {[t('license_th_id'), t('license_th_name'), t('license_th_jurisdiction'), t('license_th_reg_body'), t('license_th_issue_date'), t('license_th_expiry_date'), t('license_th_status'), t('actions')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>
@ -144,7 +145,7 @@ export const LicenseManagementPage: React.FC = () => {
}}>{l.status}</span> }}>{l.status}</span>
</td> </td>
<td style={{ padding: '10px 14px' }}> <td style={{ padding: '10px 14px' }}>
<button style={{ padding: '4px 12px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-primary)' }}></button> <button style={{ padding: '4px 12px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-primary)' }}>{t('details')}</button>
</td> </td>
</tr> </tr>
))} ))}
@ -155,7 +156,7 @@ export const LicenseManagementPage: React.FC = () => {
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
{/* Regulatory Body Mapping */} {/* Regulatory Body Mapping */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('license_reg_body_mapping')}</h2>
{regulatoryBodies.map((rb, i) => ( {regulatoryBodies.map((rb, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', padding: '12px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'center', padding: '12px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<div style={{ <div style={{
@ -177,7 +178,7 @@ export const LicenseManagementPage: React.FC = () => {
color: rb.licenses > 0 ? 'var(--color-success)' : 'var(--color-text-tertiary)', color: rb.licenses > 0 ? 'var(--color-success)' : 'var(--color-text-tertiary)',
font: 'var(--text-caption)', font: 'var(--text-caption)',
}}> }}>
{rb.licenses > 0 ? `${rb.licenses} 牌照` : '未申请'} {rb.licenses > 0 ? `${rb.licenses} ${t('license_unit')}` : t('not_applied')}
</span> </span>
</div> </div>
))} ))}
@ -185,7 +186,7 @@ export const LicenseManagementPage: React.FC = () => {
{/* Renewal Alerts */} {/* Renewal Alerts */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('license_renewal_alerts')}</h2>
{renewalAlerts.map((alert, i) => ( {renewalAlerts.map((alert, i) => (
<div key={i} style={{ <div key={i} style={{
padding: 16, marginBottom: 12, padding: 16, marginBottom: 12,
@ -199,16 +200,16 @@ export const LicenseManagementPage: React.FC = () => {
padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600, padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600,
...getUrgencyStyle(alert.urgency), ...getUrgencyStyle(alert.urgency),
}}> }}>
{alert.urgency === 'critical' ? '紧急' : alert.urgency === 'high' ? '高' : alert.urgency === 'medium' ? '中' : '低'} {alert.urgency === 'critical' ? t('urgency_critical') : alert.urgency === 'high' ? t('urgency_high') : alert.urgency === 'medium' ? t('urgency_medium') : t('urgency_low')}
</span> </span>
</div> </div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>: {alert.expiryDate}</span> <span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{t('expiry_date')}: {alert.expiryDate}</span>
<span style={{ <span style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: alert.daysRemaining <= 30 ? 'var(--color-error)' : alert.daysRemaining <= 90 ? 'var(--color-warning)' : 'var(--color-text-secondary)', color: alert.daysRemaining <= 30 ? 'var(--color-error)' : alert.daysRemaining <= 90 ? 'var(--color-warning)' : 'var(--color-text-secondary)',
}}> }}>
{alert.daysRemaining} {t('remaining_days').replace('{0}', String(alert.daysRemaining))}
</span> </span>
</div> </div>
{alert.urgency === 'critical' && ( {alert.urgency === 'critical' && (
@ -218,7 +219,7 @@ export const LicenseManagementPage: React.FC = () => {
background: 'var(--color-error)', color: 'white', background: 'var(--color-error)', color: 'white',
cursor: 'pointer', font: 'var(--text-label-sm)', cursor: 'pointer', font: 'var(--text-label-sm)',
}}> }}>
{t('renew_now')}
</button> </button>
)} )}
</div> </div>
@ -226,7 +227,7 @@ export const LicenseManagementPage: React.FC = () => {
{/* Summary */} {/* Summary */}
<div style={{ marginTop: 16, padding: 12, background: 'var(--color-gray-50)', borderRadius: 'var(--radius-sm)' }}> <div style={{ marginTop: 16, padding: 12, background: 'var(--color-gray-50)', borderRadius: 'var(--radius-sm)' }}>
<div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}> <div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}>
{licenses.filter(l => l.status === '有效').length} {new Set(licenses.filter(l => l.status === '有效').map(l => l.jurisdiction)).size} {licenses.filter(l => l.status === t('license_status_active')).length} {t('license_jurisdictions_covered').replace('{0}', String(new Set(licenses.filter(l => l.status === t('license_status_active')).map(l => l.jurisdiction)).size))}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* SEC Filing Management - SEC文件提交与管理 * SEC Filing Management - SEC文件提交与管理
@ -8,19 +9,19 @@ import React from 'react';
*/ */
const filingStats = [ const filingStats = [
{ label: '已提交文件数', value: '24', color: 'var(--color-primary)' }, { label: t('sec_filed_count'), value: '24', color: 'var(--color-primary)' },
{ label: '待审核', value: '3', color: 'var(--color-warning)' }, { label: t('sec_pending_review'), value: '3', color: 'var(--color-warning)' },
{ label: '已通过', value: '19', color: 'var(--color-success)' }, { label: t('sec_passed'), value: '19', color: 'var(--color-success)' },
{ label: '距下次截止', value: '18天', color: 'var(--color-error)' }, { label: t('sec_next_deadline'), value: '18天', color: 'var(--color-error)' },
]; ];
const secFilings = [ const secFilings = [
{ id: 'SEC-001', formType: 'S-1', title: 'IPO注册声明书', filingDate: '2026-01-15', deadline: '2026-02-28', status: '审核中', reviewer: 'SEC Division of Corporation Finance' }, { id: 'SEC-001', formType: 'S-1', title: 'IPO注册声明书', filingDate: '2026-01-15', deadline: '2026-02-28', status: t('sec_status_reviewing'), reviewer: 'SEC Division of Corporation Finance' },
{ id: 'SEC-002', formType: '10-K', title: '2025年度报告', filingDate: '2026-01-30', deadline: '2026-03-31', status: '已提交', reviewer: 'Internal Audit' }, { id: 'SEC-002', formType: '10-K', title: '2025年度报告', filingDate: '2026-01-30', deadline: '2026-03-31', status: t('sec_status_submitted'), reviewer: 'Internal Audit' },
{ id: 'SEC-003', formType: '10-Q', title: '2025 Q4季度报告', filingDate: '2026-02-01', deadline: '2026-02-15', status: '已通过', reviewer: 'External Auditor' }, { id: 'SEC-003', formType: '10-Q', title: '2025 Q4季度报告', filingDate: '2026-02-01', deadline: '2026-02-15', status: t('sec_status_passed'), reviewer: 'External Auditor' },
{ id: 'SEC-004', formType: '8-K', title: '重大事项披露-战略合作', filingDate: '2026-02-05', deadline: '2026-02-09', status: '已通过', reviewer: 'Legal Counsel' }, { id: 'SEC-004', formType: '8-K', title: '重大事项披露-战略合作', filingDate: '2026-02-05', deadline: '2026-02-09', status: t('sec_status_passed'), reviewer: 'Legal Counsel' },
{ id: 'SEC-005', formType: 'S-1/A', title: 'S-1修订稿第2版', filingDate: '2026-02-08', deadline: '2026-02-28', status: '需修订', reviewer: 'SEC Division of Corporation Finance' }, { id: 'SEC-005', formType: 'S-1/A', title: 'S-1修订稿第2版', filingDate: '2026-02-08', deadline: '2026-02-28', status: t('sec_status_needs_revision'), reviewer: 'SEC Division of Corporation Finance' },
{ id: 'SEC-006', formType: '10-Q', title: '2026 Q1季度报告', filingDate: '', deadline: '2026-05-15', status: '待提交', reviewer: '-' }, { id: 'SEC-006', formType: '10-Q', title: '2026 Q1季度报告', filingDate: '', deadline: '2026-05-15', status: t('sec_status_pending'), reviewer: '-' },
]; ];
const timelineEvents = [ const timelineEvents = [
@ -44,15 +45,15 @@ const disclosureItems = [
const getFilingStatusStyle = (status: string) => { const getFilingStatusStyle = (status: string) => {
switch (status) { switch (status) {
case '已通过': case t('sec_status_passed'):
return { background: 'var(--color-success-light)', color: 'var(--color-success)' }; return { background: 'var(--color-success-light)', color: 'var(--color-success)' };
case '审核中': case t('sec_status_reviewing'):
return { background: 'var(--color-info-light)', color: 'var(--color-info)' }; return { background: 'var(--color-info-light)', color: 'var(--color-info)' };
case '已提交': case t('sec_status_submitted'):
return { background: 'var(--color-primary-light)', color: 'var(--color-primary)' }; return { background: 'var(--color-primary-light)', color: 'var(--color-primary)' };
case '需修订': case t('sec_status_needs_revision'):
return { background: 'var(--color-error-light)', color: 'var(--color-error)' }; return { background: 'var(--color-error-light)', color: 'var(--color-error)' };
case '待提交': case t('sec_status_pending'):
return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' };
default: default:
return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' };
@ -63,7 +64,7 @@ export const SecFilingPage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}>SEC文件管理</h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}>{t('sec_title')}</h1>
<button style={{ <button style={{
padding: '8px 16px', padding: '8px 16px',
border: 'none', border: 'none',
@ -73,7 +74,7 @@ export const SecFilingPage: React.FC = () => {
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}}> }}>
+ Filing {t('sec_new_filing')}
</button> </button>
</div> </div>
@ -96,12 +97,12 @@ export const SecFilingPage: React.FC = () => {
border: '1px solid var(--color-border-light)', overflow: 'hidden', marginBottom: 24, border: '1px solid var(--color-border-light)', overflow: 'hidden', marginBottom: 24,
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>SEC申报文件列表</h2> <h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>{t('sec_filing_list')}</h2>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['编号', '表格类型', '标题', '提交日期', '截止日期', '审核方', '状态', '操作'].map(h => ( {[t('sec_th_id'), t('sec_th_form_type'), t('sec_th_title'), t('sec_th_filing_date'), t('sec_th_deadline'), t('sec_th_reviewer'), t('sec_th_status'), t('actions')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>
@ -128,7 +129,7 @@ export const SecFilingPage: React.FC = () => {
}}>{f.status}</span> }}>{f.status}</span>
</td> </td>
<td style={{ padding: '10px 14px' }}> <td style={{ padding: '10px 14px' }}>
<button style={{ padding: '4px 12px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-primary)' }}></button> <button style={{ padding: '4px 12px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-primary)' }}>{t('view')}</button>
</td> </td>
</tr> </tr>
))} ))}
@ -139,7 +140,7 @@ export const SecFilingPage: React.FC = () => {
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
{/* Filing Timeline */} {/* Filing Timeline */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('sec_timeline')}</h2>
{timelineEvents.map((evt, i) => ( {timelineEvents.map((evt, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'flex-start', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<div style={{ <div style={{
@ -159,7 +160,7 @@ export const SecFilingPage: React.FC = () => {
padding: '2px 8px', borderRadius: 'var(--radius-full)', padding: '2px 8px', borderRadius: 'var(--radius-full)',
background: 'var(--color-error-light)', color: 'var(--color-error)', background: 'var(--color-error-light)', color: 'var(--color-error)',
font: 'var(--text-caption)', whiteSpace: 'nowrap', font: 'var(--text-caption)', whiteSpace: 'nowrap',
}}></span> }}>{t('sec_upcoming')}</span>
)} )}
</div> </div>
))} ))}
@ -167,7 +168,7 @@ export const SecFilingPage: React.FC = () => {
{/* Auto-generation Status */} {/* Auto-generation Status */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('sec_disclosure_status')}</h2>
{disclosureItems.map((item, i) => ( {disclosureItems.map((item, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<div style={{ <div style={{
@ -184,14 +185,14 @@ export const SecFilingPage: React.FC = () => {
font: 'var(--text-caption)', font: 'var(--text-caption)',
color: item.status === 'done' ? 'var(--color-success)' : item.status === 'progress' ? 'var(--color-warning)' : 'var(--color-text-tertiary)', color: item.status === 'done' ? 'var(--color-success)' : item.status === 'progress' ? 'var(--color-warning)' : 'var(--color-text-tertiary)',
}}> }}>
{item.status === 'done' ? '已完成' : item.status === 'progress' ? '生成中' : '待开始'} {item.status === 'done' ? t('completed') : item.status === 'progress' ? t('generating') : t('pending')}
</span> </span>
</div> </div>
))} ))}
{/* Overall Progress */} {/* Overall Progress */}
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}></span> <span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}>{t('sec_disclosure_progress')}</span>
<span style={{ font: 'var(--text-label-sm)', color: 'var(--color-primary)' }}>57%</span> <span style={{ font: 'var(--text-label-sm)', color: 'var(--color-primary)' }}>57%</span>
</div> </div>
<div style={{ height: 8, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden' }}> <div style={{ height: 8, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden' }}>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* SOX Compliance (Sarbanes-Oxley) - SOX合规管理 * SOX Compliance (Sarbanes-Oxley) - SOX合规管理
@ -11,76 +12,76 @@ const overallScore = 78;
const controlCategories = [ const controlCategories = [
{ {
name: '财务报告内控 (ICFR)', name: 'ICFR',
description: '确保财务报告的准确性和完整性,包括收入确认、费用分摊、资产估值等关键控制点', description: 'Financial Reporting Internal Controls - Revenue recognition, expense allocation, asset valuation',
controls: [ controls: [
{ name: '收入确认控制', result: '通过', lastTest: '2026-01-15', nextTest: '2026-04-15' }, { name: 'Revenue Recognition', result: t('sox_result_passed'), lastTest: '2026-01-15', nextTest: '2026-04-15' },
{ name: '费用审批流程', result: '通过', lastTest: '2026-01-20', nextTest: '2026-04-20' }, { name: 'Expense Approval', result: t('sox_result_passed'), lastTest: '2026-01-20', nextTest: '2026-04-20' },
{ name: '期末关账控制', result: '发现缺陷', lastTest: '2026-02-01', nextTest: '2026-03-01' }, { name: 'Period-end Close', result: t('sox_result_defect'), lastTest: '2026-02-01', nextTest: '2026-03-01' },
{ name: '合并报表控制', result: '通过', lastTest: '2026-01-25', nextTest: '2026-04-25' }, { name: 'Consolidation', result: t('sox_result_passed'), lastTest: '2026-01-25', nextTest: '2026-04-25' },
], ],
}, },
{ {
name: 'IT通用控制 (ITGC)', name: 'ITGC',
description: '信息系统通用控制,包括系统开发、程序变更、计算机操作和数据安全', description: 'IT General Controls - System development, program change, computer operations, data security',
controls: [ controls: [
{ name: '系统开发生命周期', result: '通过', lastTest: '2026-01-10', nextTest: '2026-04-10' }, { name: 'SDLC', result: t('sox_result_passed'), lastTest: '2026-01-10', nextTest: '2026-04-10' },
{ name: '程序变更管理', result: '通过', lastTest: '2026-01-18', nextTest: '2026-04-18' }, { name: 'Change Management', result: t('sox_result_passed'), lastTest: '2026-01-18', nextTest: '2026-04-18' },
{ name: '数据备份与恢复', result: '发现缺陷', lastTest: '2026-02-03', nextTest: '2026-03-03' }, { name: 'Backup & Recovery', result: t('sox_result_defect'), lastTest: '2026-02-03', nextTest: '2026-03-03' },
{ name: '逻辑安全控制', result: '待测试', lastTest: '-', nextTest: '2026-02-20' }, { name: 'Logical Security', result: t('sox_result_pending'), lastTest: '-', nextTest: '2026-02-20' },
], ],
}, },
{ {
name: '访问控制', name: 'Access Control',
description: '系统与数据访问权限管理,包括用户权限分配、特权账户管理、职责分离', description: 'System & data access management - User permissions, privileged accounts, SoD',
controls: [ controls: [
{ name: '用户权限审查', result: '通过', lastTest: '2026-02-01', nextTest: '2026-05-01' }, { name: 'User Access Review', result: t('sox_result_passed'), lastTest: '2026-02-01', nextTest: '2026-05-01' },
{ name: '特权账户管理', result: '通过', lastTest: '2026-01-28', nextTest: '2026-04-28' }, { name: 'Privileged Access', result: t('sox_result_passed'), lastTest: '2026-01-28', nextTest: '2026-04-28' },
{ name: '职责分离 (SoD)', result: '发现缺陷', lastTest: '2026-02-05', nextTest: '2026-03-05' }, { name: 'SoD', result: t('sox_result_defect'), lastTest: '2026-02-05', nextTest: '2026-03-05' },
], ],
}, },
{ {
name: '变更管理', name: 'Change Management',
description: '对生产环境变更的审批、测试、部署流程控制', description: 'Production change approval, testing, deployment process controls',
controls: [ controls: [
{ name: '变更审批流程', result: '通过', lastTest: '2026-01-22', nextTest: '2026-04-22' }, { name: 'Change Approval', result: t('sox_result_passed'), lastTest: '2026-01-22', nextTest: '2026-04-22' },
{ name: '部署前测试验证', result: '通过', lastTest: '2026-01-30', nextTest: '2026-04-30' }, { name: 'Pre-deploy Testing', result: t('sox_result_passed'), lastTest: '2026-01-30', nextTest: '2026-04-30' },
{ name: '紧急变更管理', result: '待测试', lastTest: '-', nextTest: '2026-02-25' }, { name: 'Emergency Change', result: t('sox_result_pending'), lastTest: '-', nextTest: '2026-02-25' },
], ],
}, },
{ {
name: '运营控制', name: 'Operational Controls',
description: '日常运营流程控制,包括交易监控、对账、异常处理', description: 'Daily operations - Transaction monitoring, reconciliation, exception handling',
controls: [ controls: [
{ name: '日终对账', result: '通过', lastTest: '2026-02-08', nextTest: '2026-05-08' }, { name: 'EOD Reconciliation', result: t('sox_result_passed'), lastTest: '2026-02-08', nextTest: '2026-05-08' },
{ name: '异常交易监控', result: '通过', lastTest: '2026-02-06', nextTest: '2026-05-06' }, { name: 'Anomaly Monitoring', result: t('sox_result_passed'), lastTest: '2026-02-06', nextTest: '2026-05-06' },
{ name: '客户资金隔离', result: '通过', lastTest: '2026-02-04', nextTest: '2026-05-04' }, { name: 'Client Fund Segregation', result: t('sox_result_passed'), lastTest: '2026-02-04', nextTest: '2026-05-04' },
], ],
}, },
]; ];
const deficiencies = [ const deficiencies = [
{ id: 'DEF-001', control: '期末关账控制', category: 'ICFR', severity: '重大缺陷', description: '部分手工调整缺少二级审批', foundDate: '2026-02-01', dueDate: '2026-03-01', status: '整改中', owner: 'CFO办公室' }, { id: 'DEF-001', control: 'Period-end Close', category: 'ICFR', severity: t('sox_severity_major'), description: 'Manual adjustments missing secondary approval', foundDate: '2026-02-01', dueDate: '2026-03-01', status: t('sox_status_remediating'), owner: 'CFO Office' },
{ id: 'DEF-002', control: '数据备份与恢复', category: 'ITGC', severity: '一般缺陷', description: 'DR演练未按季度执行', foundDate: '2026-02-03', dueDate: '2026-03-15', status: '整改中', owner: 'IT部门' }, { id: 'DEF-002', control: 'Backup & Recovery', category: 'ITGC', severity: t('sox_severity_minor'), description: 'DR drill not executed quarterly', foundDate: '2026-02-03', dueDate: '2026-03-15', status: t('sox_status_remediating'), owner: 'IT Dept' },
{ id: 'DEF-003', control: '职责分离 (SoD)', category: '访问控制', severity: '重大缺陷', description: '3名用户同时拥有创建与审批权限', foundDate: '2026-02-05', dueDate: '2026-02-20', status: '待整改', owner: '合规部门' }, { id: 'DEF-003', control: 'SoD', category: 'Access Control', severity: t('sox_severity_major'), description: '3 users with both create & approve access', foundDate: '2026-02-05', dueDate: '2026-02-20', status: t('sox_status_pending'), owner: 'Compliance' },
]; ];
const auditorReview = [ const auditorReview = [
{ phase: '审计计划确认', status: 'done', date: '2026-01-05', auditor: 'Deloitte' }, { phase: 'Audit Plan Confirmation', status: 'done', date: '2026-01-05', auditor: 'Deloitte' },
{ phase: 'Walk-through 测试', status: 'done', date: '2026-01-20', auditor: 'Deloitte' }, { phase: 'Walk-through Testing', status: 'done', date: '2026-01-20', auditor: 'Deloitte' },
{ phase: '控制有效性测试', status: 'progress', date: '2026-02-10', auditor: 'Deloitte' }, { phase: 'Controls Effectiveness Testing', status: 'progress', date: '2026-02-10', auditor: 'Deloitte' },
{ phase: '缺陷评估与分类', status: 'pending', date: '2026-03-01', auditor: 'Deloitte' }, { phase: 'Deficiency Assessment', status: 'pending', date: '2026-03-01', auditor: 'Deloitte' },
{ phase: '管理层报告出具', status: 'pending', date: '2026-03-15', auditor: 'Deloitte' }, { phase: 'Management Report', status: 'pending', date: '2026-03-15', auditor: 'Deloitte' },
{ phase: '最终审计意见', status: 'pending', date: '2026-04-01', auditor: 'Deloitte' }, { phase: 'Final Audit Opinion', status: 'pending', date: '2026-04-01', auditor: 'Deloitte' },
]; ];
const getResultStyle = (result: string) => { const getResultStyle = (result: string) => {
switch (result) { switch (result) {
case '通过': case t('sox_result_passed'):
return { background: 'var(--color-success-light)', color: 'var(--color-success)' }; return { background: 'var(--color-success-light)', color: 'var(--color-success)' };
case '发现缺陷': case t('sox_result_defect'):
return { background: 'var(--color-error-light)', color: 'var(--color-error)' }; return { background: 'var(--color-error-light)', color: 'var(--color-error)' };
case '待测试': case t('sox_result_pending'):
return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' };
default: default:
return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' };
@ -89,11 +90,11 @@ const getResultStyle = (result: string) => {
const getSeverityStyle = (severity: string) => { const getSeverityStyle = (severity: string) => {
switch (severity) { switch (severity) {
case '重大缺陷': case t('sox_severity_major'):
return { background: 'var(--color-error-light)', color: 'var(--color-error)' }; return { background: 'var(--color-error-light)', color: 'var(--color-error)' };
case '一般缺陷': case t('sox_severity_minor'):
return { background: 'var(--color-warning-light)', color: 'var(--color-warning)' }; return { background: 'var(--color-warning-light)', color: 'var(--color-warning)' };
case '观察项': case t('sox_severity_observation'):
return { background: 'var(--color-info-light)', color: 'var(--color-info)' }; return { background: 'var(--color-info-light)', color: 'var(--color-info)' };
default: default:
return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' };
@ -102,13 +103,13 @@ const getSeverityStyle = (severity: string) => {
export const SoxCompliancePage: React.FC = () => { export const SoxCompliancePage: React.FC = () => {
const totalControls = controlCategories.reduce((sum, cat) => sum + cat.controls.length, 0); const totalControls = controlCategories.reduce((sum, cat) => sum + cat.controls.length, 0);
const passedControls = controlCategories.reduce((sum, cat) => sum + cat.controls.filter(c => c.result === '通过').length, 0); const passedControls = controlCategories.reduce((sum, cat) => sum + cat.controls.filter(c => c.result === t('sox_result_passed')).length, 0);
const defectControls = controlCategories.reduce((sum, cat) => sum + cat.controls.filter(c => c.result === '发现缺陷').length, 0); const defectControls = controlCategories.reduce((sum, cat) => sum + cat.controls.filter(c => c.result === t('sox_result_defect')).length, 0);
const pendingControls = controlCategories.reduce((sum, cat) => sum + cat.controls.filter(c => c.result === '待测试').length, 0); const pendingControls = controlCategories.reduce((sum, cat) => sum + cat.controls.filter(c => c.result === t('sox_result_pending')).length, 0);
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>SOX合规管理</h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>{t('sox_title')}</h1>
{/* Compliance Score Gauge + Summary Stats */} {/* Compliance Score Gauge + Summary Stats */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 3fr', gap: 24, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 3fr', gap: 24, marginBottom: 24 }}>
@ -118,7 +119,7 @@ export const SoxCompliancePage: React.FC = () => {
border: '1px solid var(--color-border-light)', padding: 24, border: '1px solid var(--color-border-light)', padding: 24,
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
}}> }}>
<div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', marginBottom: 12 }}></div> <div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', marginBottom: 12 }}>{t('sox_overall_score')}</div>
<div style={{ <div style={{
width: 120, height: 120, borderRadius: '50%', position: 'relative', width: 120, height: 120, borderRadius: '50%', position: 'relative',
background: `conic-gradient(${overallScore >= 80 ? 'var(--color-success)' : overallScore >= 60 ? 'var(--color-warning)' : 'var(--color-error)'} ${overallScore * 3.6}deg, var(--color-gray-100) 0deg)`, background: `conic-gradient(${overallScore >= 80 ? 'var(--color-success)' : overallScore >= 60 ? 'var(--color-warning)' : 'var(--color-error)'} ${overallScore * 3.6}deg, var(--color-gray-100) 0deg)`,
@ -133,16 +134,16 @@ export const SoxCompliancePage: React.FC = () => {
</span> </span>
</div> </div>
</div> </div>
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginTop: 8 }}> 100</div> <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginTop: 8 }}>{t('sox_full_score')}</div>
</div> </div>
{/* Summary Stats */} {/* Summary Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16 }}>
{[ {[
{ label: '总控制点', value: String(totalControls), color: 'var(--color-primary)' }, { label: t('sox_total_controls'), value: String(totalControls), color: 'var(--color-primary)' },
{ label: '测试通过', value: String(passedControls), color: 'var(--color-success)' }, { label: t('sox_test_passed'), value: String(passedControls), color: 'var(--color-success)' },
{ label: '发现缺陷', value: String(defectControls), color: 'var(--color-error)' }, { label: t('sox_defects_found'), value: String(defectControls), color: 'var(--color-error)' },
{ label: '待测试', value: String(pendingControls), color: 'var(--color-warning)' }, { label: t('sox_pending_test'), value: String(pendingControls), color: 'var(--color-warning)' },
].map(s => ( ].map(s => (
<div key={s.label} style={{ <div key={s.label} style={{
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
@ -161,7 +162,7 @@ export const SoxCompliancePage: React.FC = () => {
border: '1px solid var(--color-border-light)', marginBottom: 24, overflow: 'hidden', border: '1px solid var(--color-border-light)', marginBottom: 24, overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}></h2> <h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>{t('sox_control_categories')}</h2>
</div> </div>
{controlCategories.map((cat, catIdx) => ( {controlCategories.map((cat, catIdx) => (
<div key={catIdx} style={{ borderBottom: catIdx < controlCategories.length - 1 ? '2px solid var(--color-border-light)' : 'none' }}> <div key={catIdx} style={{ borderBottom: catIdx < controlCategories.length - 1 ? '2px solid var(--color-border-light)' : 'none' }}>
@ -174,7 +175,7 @@ export const SoxCompliancePage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr> <tr>
{['控制点', '测试结果', '上次测试', '下次测试'].map(h => ( {[t('sox_th_control_point'), t('sox_th_test_result'), t('sox_th_last_test'), t('sox_th_next_test')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '8px 20px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '8px 20px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>
@ -206,12 +207,12 @@ export const SoxCompliancePage: React.FC = () => {
border: '1px solid var(--color-border-light)', overflow: 'hidden', border: '1px solid var(--color-border-light)', overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}></h2> <h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>{t('sox_deficiency_tracking')}</h2>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['编号', '控制点', '严重程度', '描述', '整改期限', '状态', '负责方'].map(h => ( {[t('sox_th_id'), t('sox_th_control'), t('sox_th_severity'), t('sox_th_description'), t('sox_th_due_date'), t('sox_th_status'), t('sox_th_owner')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>
@ -232,8 +233,8 @@ export const SoxCompliancePage: React.FC = () => {
<td style={{ padding: '10px 14px' }}> <td style={{ padding: '10px 14px' }}>
<span style={{ <span style={{
padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600, padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600,
background: d.status === '整改中' ? 'var(--color-warning-light)' : 'var(--color-error-light)', background: d.status === t('sox_status_remediating') ? 'var(--color-warning-light)' : 'var(--color-error-light)',
color: d.status === '整改中' ? 'var(--color-warning)' : 'var(--color-error)', color: d.status === t('sox_status_remediating') ? 'var(--color-warning)' : 'var(--color-error)',
}}>{d.status}</span> }}>{d.status}</span>
</td> </td>
<td style={{ font: 'var(--text-caption)', padding: '10px 14px', color: 'var(--color-text-tertiary)' }}>{d.owner}</td> <td style={{ font: 'var(--text-caption)', padding: '10px 14px', color: 'var(--color-text-tertiary)' }}>{d.owner}</td>
@ -245,7 +246,7 @@ export const SoxCompliancePage: React.FC = () => {
{/* Auditor Review Status */} {/* Auditor Review Status */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 4 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 4 }}>{t('sox_auditor_progress')}</h2>
<div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginBottom: 16 }}>External Auditor: Deloitte</div> <div style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginBottom: 16 }}>External Auditor: Deloitte</div>
{auditorReview.map((phase, i) => ( {auditorReview.map((phase, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'flex-start', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}>
@ -265,14 +266,14 @@ export const SoxCompliancePage: React.FC = () => {
font: 'var(--text-caption)', font: 'var(--text-caption)',
color: phase.status === 'done' ? 'var(--color-success)' : phase.status === 'progress' ? 'var(--color-warning)' : 'var(--color-text-tertiary)', color: phase.status === 'done' ? 'var(--color-success)' : phase.status === 'progress' ? 'var(--color-warning)' : 'var(--color-text-tertiary)',
}}> }}>
{phase.status === 'done' ? '已完成' : phase.status === 'progress' ? '进行中' : '待开始'} {phase.status === 'done' ? t('completed') : phase.status === 'progress' ? t('in_progress') : t('pending')}
</span> </span>
</div> </div>
))} ))}
{/* Progress Bar */} {/* Progress Bar */}
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}></span> <span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}>{t('sox_audit_progress')}</span>
<span style={{ font: 'var(--text-label-sm)', color: 'var(--color-primary)' }}>33%</span> <span style={{ font: 'var(--text-label-sm)', color: 'var(--color-primary)' }}>33%</span>
</div> </div>
<div style={{ height: 8, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden' }}> <div style={{ height: 8, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden' }}>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* Tax Compliance Management - * Tax Compliance Management -
@ -8,22 +9,22 @@ import React from 'react';
*/ */
const taxStats = [ const taxStats = [
{ label: '应纳税额', value: '$1,245,890', color: 'var(--color-primary)' }, { label: t('tax_payable'), value: '$1,245,890', color: 'var(--color-primary)' },
{ label: '已缴税额', value: '$982,450', color: 'var(--color-success)' }, { label: t('tax_paid'), value: '$982,450', color: 'var(--color-success)' },
{ label: '税务合规率', value: '96.8%', color: 'var(--color-info)' }, { label: t('tax_compliance_rate'), value: '96.8%', color: 'var(--color-info)' },
{ label: '待处理事项', value: '5', color: 'var(--color-warning)' }, { label: t('tax_pending_items'), value: '5', color: 'var(--color-warning)' },
]; ];
const taxObligations = [ const taxObligations = [
{ jurisdiction: 'Federal', taxType: 'Corporate Income Tax', period: 'FY 2025', amount: '$425,000', paid: '$425,000', status: '已缴', dueDate: '2026-04-15' }, { jurisdiction: 'Federal', taxType: 'Corporate Income Tax', period: 'FY 2025', amount: '$425,000', paid: '$425,000', status: t('tax_status_paid'), dueDate: '2026-04-15' },
{ jurisdiction: 'Federal', taxType: 'Employment Tax (FICA)', period: 'Q4 2025', amount: '$68,200', paid: '$68,200', status: '已缴', dueDate: '2026-01-31' }, { jurisdiction: 'Federal', taxType: 'Employment Tax (FICA)', period: 'Q4 2025', amount: '$68,200', paid: '$68,200', status: t('tax_status_paid'), dueDate: '2026-01-31' },
{ jurisdiction: 'California', taxType: 'State Income Tax', amount: '$187,500', paid: '$187,500', period: 'FY 2025', status: '已缴', dueDate: '2026-04-15' }, { jurisdiction: 'California', taxType: 'State Income Tax', amount: '$187,500', paid: '$187,500', period: 'FY 2025', status: t('tax_status_paid'), dueDate: '2026-04-15' },
{ jurisdiction: 'California', taxType: 'Sales & Use Tax', amount: '$42,300', paid: '$42,300', period: 'Q4 2025', status: '已缴', dueDate: '2026-01-31' }, { jurisdiction: 'California', taxType: 'Sales & Use Tax', amount: '$42,300', paid: '$42,300', period: 'Q4 2025', status: t('tax_status_paid'), dueDate: '2026-01-31' },
{ jurisdiction: 'New York', taxType: 'State Income Tax', amount: '$156,800', paid: '$120,000', period: 'FY 2025', status: '部分缴纳', dueDate: '2026-04-15' }, { jurisdiction: 'New York', taxType: 'State Income Tax', amount: '$156,800', paid: '$120,000', period: 'FY 2025', status: t('tax_status_partial'), dueDate: '2026-04-15' },
{ jurisdiction: 'New York', taxType: 'Metropolitan Commuter Tax', amount: '$12,400', paid: '$0', period: 'FY 2025', status: '待缴', dueDate: '2026-04-15' }, { jurisdiction: 'New York', taxType: 'Metropolitan Commuter Tax', amount: '$12,400', paid: '$0', period: 'FY 2025', status: t('tax_status_unpaid'), dueDate: '2026-04-15' },
{ jurisdiction: 'Texas', taxType: 'Franchise Tax', amount: '$34,600', paid: '$34,600', period: 'FY 2025', status: '已缴', dueDate: '2026-05-15' }, { jurisdiction: 'Texas', taxType: 'Franchise Tax', amount: '$34,600', paid: '$34,600', period: 'FY 2025', status: t('tax_status_paid'), dueDate: '2026-05-15' },
{ jurisdiction: 'Florida', taxType: 'Sales Tax', amount: '$28,900', paid: '$28,900', period: 'Q4 2025', status: '已缴', dueDate: '2026-01-31' }, { jurisdiction: 'Florida', taxType: 'Sales Tax', amount: '$28,900', paid: '$28,900', period: 'Q4 2025', status: t('tax_status_paid'), dueDate: '2026-01-31' },
{ jurisdiction: 'Federal', taxType: 'Estimated Tax (Q1 2026)', amount: '$263,190', paid: '$0', period: 'Q1 2026', status: '待缴', dueDate: '2026-04-15' }, { jurisdiction: 'Federal', taxType: 'Estimated Tax (Q1 2026)', amount: '$263,190', paid: '$0', period: 'Q1 2026', status: t('tax_status_unpaid'), dueDate: '2026-04-15' },
]; ];
const taxTypeBreakdown = [ const taxTypeBreakdown = [
@ -35,14 +36,14 @@ const taxTypeBreakdown = [
]; ];
const irsFilings = [ const irsFilings = [
{ form: 'Form 1120', description: '公司所得税申报', taxYear: '2025', deadline: '2026-04-15', status: '准备中', filedDate: '-' }, { form: 'Form 1120', description: 'Corporate Income Tax', taxYear: '2025', deadline: '2026-04-15', status: t('tax_filing_preparing'), filedDate: '-' },
{ form: 'Form 1099-K', description: '支付卡和第三方网络交易', taxYear: '2025', deadline: '2026-01-31', status: '已提交', filedDate: '2026-01-28' }, { form: 'Form 1099-K', description: 'Payment Card & Third-party Network', taxYear: '2025', deadline: '2026-01-31', status: t('tax_filing_submitted'), filedDate: '2026-01-28' },
{ form: 'Form 1099-MISC', description: '杂项收入(承包商支付)', taxYear: '2025', deadline: '2026-01-31', status: '已提交', filedDate: '2026-01-29' }, { form: 'Form 1099-MISC', description: 'Miscellaneous Income (Contractors)', taxYear: '2025', deadline: '2026-01-31', status: t('tax_filing_submitted'), filedDate: '2026-01-29' },
{ form: 'Form 1099-NEC', description: '非雇员报酬', taxYear: '2025', deadline: '2026-01-31', status: '已提交', filedDate: '2026-01-30' }, { form: 'Form 1099-NEC', description: 'Non-employee Compensation', taxYear: '2025', deadline: '2026-01-31', status: t('tax_filing_submitted'), filedDate: '2026-01-30' },
{ form: 'Form 941', description: '雇主季度联邦税', taxYear: 'Q4 2025', deadline: '2026-01-31', status: '已提交', filedDate: '2026-01-25' }, { form: 'Form 941', description: 'Employer Quarterly Federal Tax', taxYear: 'Q4 2025', deadline: '2026-01-31', status: t('tax_filing_submitted'), filedDate: '2026-01-25' },
{ form: 'Form W-2', description: '工资与税务声明', taxYear: '2025', deadline: '2026-01-31', status: '已提交', filedDate: '2026-01-27' }, { form: 'Form W-2', description: 'Wage & Tax Statement', taxYear: '2025', deadline: '2026-01-31', status: t('tax_filing_submitted'), filedDate: '2026-01-27' },
{ form: 'Form 1042-S', description: '外国人预扣所得', taxYear: '2025', deadline: '2026-03-15', status: '准备中', filedDate: '-' }, { form: 'Form 1042-S', description: 'Foreign Person Withholding', taxYear: '2025', deadline: '2026-03-15', status: t('tax_filing_preparing'), filedDate: '-' },
{ form: 'Form 8300', description: '现金支付超$10,000报告', taxYear: '2025', deadline: '交易后15天', status: '按需提交', filedDate: '-' }, { form: 'Form 8300', description: 'Cash Payments Over $10,000', taxYear: '2025', deadline: '15 days after txn', status: t('tax_filing_on_demand'), filedDate: '-' },
]; ];
const taxDeadlines = [ const taxDeadlines = [
@ -58,11 +59,11 @@ const taxDeadlines = [
const getPaymentStatusStyle = (status: string) => { const getPaymentStatusStyle = (status: string) => {
switch (status) { switch (status) {
case '已缴': case t('tax_status_paid'):
return { background: 'var(--color-success-light)', color: 'var(--color-success)' }; return { background: 'var(--color-success-light)', color: 'var(--color-success)' };
case '部分缴纳': case t('tax_status_partial'):
return { background: 'var(--color-warning-light)', color: 'var(--color-warning)' }; return { background: 'var(--color-warning-light)', color: 'var(--color-warning)' };
case '待缴': case t('tax_status_unpaid'):
return { background: 'var(--color-error-light)', color: 'var(--color-error)' }; return { background: 'var(--color-error-light)', color: 'var(--color-error)' };
default: default:
return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' };
@ -71,13 +72,13 @@ const getPaymentStatusStyle = (status: string) => {
const getFilingStatusStyle = (status: string) => { const getFilingStatusStyle = (status: string) => {
switch (status) { switch (status) {
case '已提交': case t('tax_filing_submitted'):
return { background: 'var(--color-success-light)', color: 'var(--color-success)' }; return { background: 'var(--color-success-light)', color: 'var(--color-success)' };
case '准备中': case t('tax_filing_preparing'):
return { background: 'var(--color-warning-light)', color: 'var(--color-warning)' }; return { background: 'var(--color-warning-light)', color: 'var(--color-warning)' };
case '按需提交': case t('tax_filing_on_demand'):
return { background: 'var(--color-info-light)', color: 'var(--color-info)' }; return { background: 'var(--color-info-light)', color: 'var(--color-info)' };
case '逾期': case t('tax_filing_overdue'):
return { background: 'var(--color-error-light)', color: 'var(--color-error)' }; return { background: 'var(--color-error-light)', color: 'var(--color-error)' };
default: default:
return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }; return { background: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' };
@ -88,7 +89,7 @@ export const TaxCompliancePage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}>{t('tax_title')}</h1>
<button style={{ <button style={{
padding: '8px 16px', padding: '8px 16px',
border: 'none', border: 'none',
@ -98,7 +99,7 @@ export const TaxCompliancePage: React.FC = () => {
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}}> }}>
{t('tax_export_report')}
</button> </button>
</div> </div>
@ -121,37 +122,37 @@ export const TaxCompliancePage: React.FC = () => {
border: '1px solid var(--color-border-light)', overflow: 'hidden', marginBottom: 24, border: '1px solid var(--color-border-light)', overflow: 'hidden', marginBottom: 24,
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}></h2> <h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>{t('tax_obligations')}</h2>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['管辖区', '税种', '期间', '应缴金额', '已缴金额', '截止日期', '状态'].map(h => ( {[t('tax_th_jurisdiction'), t('tax_th_type'), t('tax_th_period'), t('tax_th_payable'), t('tax_th_paid'), t('tax_th_deadline'), t('tax_th_status')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{taxObligations.map((t, i) => ( {taxObligations.map((tax, i) => (
<tr key={i} style={{ borderBottom: '1px solid var(--color-border-light)' }}> <tr key={i} style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ padding: '10px 14px' }}> <td style={{ padding: '10px 14px' }}>
<span style={{ <span style={{
padding: '2px 8px', borderRadius: 'var(--radius-full)', padding: '2px 8px', borderRadius: 'var(--radius-full)',
background: t.jurisdiction === 'Federal' ? 'var(--color-primary-light)' : 'var(--color-info-light)', background: tax.jurisdiction === 'Federal' ? 'var(--color-primary-light)' : 'var(--color-info-light)',
color: t.jurisdiction === 'Federal' ? 'var(--color-primary)' : 'var(--color-info)', color: tax.jurisdiction === 'Federal' ? 'var(--color-primary)' : 'var(--color-info)',
font: 'var(--text-caption)', fontWeight: 600, font: 'var(--text-caption)', fontWeight: 600,
}}>{t.jurisdiction}</span> }}>{tax.jurisdiction}</span>
</td> </td>
<td style={{ font: 'var(--text-body-sm)', padding: '10px 14px', color: 'var(--color-text-primary)' }}>{t.taxType}</td> <td style={{ font: 'var(--text-body-sm)', padding: '10px 14px', color: 'var(--color-text-primary)' }}>{tax.taxType}</td>
<td style={{ font: 'var(--text-body-sm)', padding: '10px 14px', color: 'var(--color-text-tertiary)' }}>{t.period}</td> <td style={{ font: 'var(--text-body-sm)', padding: '10px 14px', color: 'var(--color-text-tertiary)' }}>{tax.period}</td>
<td style={{ font: 'var(--text-label-sm)', padding: '10px 14px', color: 'var(--color-text-primary)' }}>{t.amount}</td> <td style={{ font: 'var(--text-label-sm)', padding: '10px 14px', color: 'var(--color-text-primary)' }}>{tax.amount}</td>
<td style={{ font: 'var(--text-label-sm)', padding: '10px 14px', color: 'var(--color-success)' }}>{t.paid}</td> <td style={{ font: 'var(--text-label-sm)', padding: '10px 14px', color: 'var(--color-success)' }}>{tax.paid}</td>
<td style={{ font: 'var(--text-body-sm)', padding: '10px 14px', color: 'var(--color-text-tertiary)' }}>{t.dueDate}</td> <td style={{ font: 'var(--text-body-sm)', padding: '10px 14px', color: 'var(--color-text-tertiary)' }}>{tax.dueDate}</td>
<td style={{ padding: '10px 14px' }}> <td style={{ padding: '10px 14px' }}>
<span style={{ <span style={{
padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600, padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600,
...getPaymentStatusStyle(t.status), ...getPaymentStatusStyle(tax.status),
}}>{t.status}</span> }}>{tax.status}</span>
</td> </td>
</tr> </tr>
))} ))}
@ -167,12 +168,12 @@ export const TaxCompliancePage: React.FC = () => {
border: '1px solid var(--color-border-light)', overflow: 'hidden', border: '1px solid var(--color-border-light)', overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}></h2> <h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>{t('tax_type_breakdown')}</h2>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['税种', '联邦', '州级', '合计', '占比'].map(h => ( {[t('tax_th_tax_type'), t('tax_th_federal'), t('tax_th_state'), t('tax_th_total'), t('tax_th_percentage')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>
@ -200,7 +201,7 @@ export const TaxCompliancePage: React.FC = () => {
{/* Tax Calendar / Deadlines */} {/* Tax Calendar / Deadlines */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('tax_calendar')}</h2>
{taxDeadlines.map((evt, i) => ( {taxDeadlines.map((evt, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'flex-start', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<div style={{ <div style={{
@ -219,7 +220,7 @@ export const TaxCompliancePage: React.FC = () => {
font: 'var(--text-caption)', font: 'var(--text-caption)',
color: evt.done ? 'var(--color-success)' : 'var(--color-error)', color: evt.done ? 'var(--color-success)' : 'var(--color-error)',
}}> }}>
{evt.done ? '已完成' : '待处理'} {evt.done ? t('tax_status_done') : t('tax_status_pending')}
</span> </span>
</div> </div>
))} ))}
@ -232,12 +233,12 @@ export const TaxCompliancePage: React.FC = () => {
border: '1px solid var(--color-border-light)', overflow: 'hidden', border: '1px solid var(--color-border-light)', overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)' }}>
<h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>IRS表格提交追踪</h2> <h2 style={{ font: 'var(--text-h2)', color: 'var(--color-text-primary)' }}>{t('tax_irs_tracker')}</h2>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['表格', '说明', '税务年度', '截止日期', '提交日期', '状态'].map(h => ( {[t('tax_th_form'), t('tax_th_description'), t('tax_th_tax_year'), t('tax_th_deadline'), t('tax_th_filed_date'), t('tax_th_status')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>

View File

@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { t } from '@/i18n/locales';
/** /**
* D2. - * D2. -
@ -35,10 +36,10 @@ const statusColors: Record<string, string> = {
expired: 'var(--color-text-tertiary)', expired: 'var(--color-text-tertiary)',
}; };
const statusLabels: Record<string, string> = { const statusLabels: Record<string, string> = {
pending: '待审核', pending: t('coupon_pending_review'),
active: '在售中', active: t('coupon_active'),
suspended: '已暂停', suspended: t('coupon_suspended'),
expired: '已过期', expired: t('coupon_expired'),
}; };
export const CouponManagementPage: React.FC = () => { export const CouponManagementPage: React.FC = () => {
@ -49,7 +50,7 @@ export const CouponManagementPage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}>{t('coupon_management_title')}</h1>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
{['all', 'pending', 'active', 'suspended', 'expired'].map(f => ( {['all', 'pending', 'active', 'suspended', 'expired'].map(f => (
<button key={f} onClick={() => setFilter(f)} style={{ <button key={f} onClick={() => setFilter(f)} style={{
@ -58,7 +59,7 @@ export const CouponManagementPage: React.FC = () => {
color: filter === f ? 'white' : 'var(--color-text-secondary)', color: filter === f ? 'white' : 'var(--color-text-secondary)',
cursor: 'pointer', font: 'var(--text-label-sm)', cursor: 'pointer', font: 'var(--text-label-sm)',
}}> }}>
{f === 'all' ? '全部' : statusLabels[f]} {f === 'all' ? t('all') : statusLabels[f]}
</button> </button>
))} ))}
</div> </div>
@ -69,7 +70,7 @@ export const CouponManagementPage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ borderBottom: '1px solid var(--color-border-light)' }}> <tr style={{ borderBottom: '1px solid var(--color-border-light)' }}>
{['券ID', '发行方', '券名称', '模板', '面值', '发行量', '已售', '已核销', '状态', '操作'].map(h => ( {[t('coupon_id'), t('coupon_issuer'), t('coupon_name'), t('coupon_template'), t('coupon_face_value'), t('coupon_quantity'), t('coupon_sold'), t('coupon_redeemed'), t('status'), t('actions')].map(h => (
<th key={h} style={{ padding: '12px 16px', textAlign: 'left', font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)' }}>{h}</th> <th key={h} style={{ padding: '12px 16px', textAlign: 'left', font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)' }}>{h}</th>
))} ))}
</tr> </tr>
@ -95,12 +96,12 @@ export const CouponManagementPage: React.FC = () => {
<td style={cellStyle}> <td style={cellStyle}>
{coupon.status === 'pending' && ( {coupon.status === 'pending' && (
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
<button style={btnStyle('var(--color-success)')}></button> <button style={btnStyle('var(--color-success)')}>{t('coupon_approve')}</button>
<button style={btnStyle('var(--color-error)')}></button> <button style={btnStyle('var(--color-error)')}>{t('coupon_reject')}</button>
</div> </div>
)} )}
{coupon.status === 'active' && ( {coupon.status === 'active' && (
<button style={btnStyle('var(--color-warning)')}></button> <button style={btnStyle('var(--color-warning)')}>{t('suspend')}</button>
)} )}
</td> </td>
</tr> </tr>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D1. * D1.
@ -16,19 +17,19 @@ interface StatCard {
} }
const stats: StatCard[] = [ const stats: StatCard[] = [
{ label: '总交易量', value: '156,890', change: '+12.3%', trend: 'up', color: 'var(--color-primary)' }, { label: t('dashboard_total_volume'), value: '156,890', change: '+12.3%', trend: 'up', color: 'var(--color-primary)' },
{ label: '交易金额', value: '$4,523,456', change: '+8.7%', trend: 'up', color: 'var(--color-success)' }, { label: t('dashboard_total_amount'), value: '$4,523,456', change: '+8.7%', trend: 'up', color: 'var(--color-success)' },
{ label: '活跃用户', value: '28,456', change: '+5.2%', trend: 'up', color: 'var(--color-info)' }, { label: t('dashboard_active_users'), value: '28,456', change: '+5.2%', trend: 'up', color: 'var(--color-info)' },
{ label: '发行方数量', value: '342', change: '+15', trend: 'up', color: 'var(--color-warning)' }, { label: t('dashboard_issuer_count'), value: '342', change: '+15', trend: 'up', color: 'var(--color-warning)' },
{ label: '券流通总量', value: '1,234,567', change: '-2.1%', trend: 'down', color: 'var(--color-primary-dark)' }, { label: t('dashboard_coupon_circulation'), value: '1,234,567', change: '-2.1%', trend: 'down', color: 'var(--color-primary-dark)' },
{ label: '系统健康', value: '99.97%', change: 'Normal', trend: 'up', color: 'var(--color-success)' }, { label: t('dashboard_system_health'), value: '99.97%', change: 'Normal', trend: 'up', color: 'var(--color-success)' },
]; ];
export const DashboardPage: React.FC = () => { export const DashboardPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>
{t('dashboard_title')}
</h1> </h1>
{/* Stats Grid */} {/* Stats Grid */}
@ -71,7 +72,7 @@ export const DashboardPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('dashboard_volume_trend')}</div>
<div style={{ <div style={{
height: 240, height: 240,
background: 'var(--color-gray-50)', background: 'var(--color-gray-50)',
@ -92,7 +93,7 @@ export const DashboardPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('dashboard_type_distribution')}</div>
<div style={{ <div style={{
height: 240, height: 240,
background: 'var(--color-gray-50)', background: 'var(--color-gray-50)',
@ -117,7 +118,7 @@ export const DashboardPage: React.FC = () => {
padding: 20, padding: 20,
}}> }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<div style={{ font: 'var(--text-h3)' }}></div> <div style={{ font: 'var(--text-h3)' }}>{t('dashboard_realtime_feed')}</div>
<span style={{ <span style={{
width: 8, height: 8, width: 8, height: 8,
borderRadius: '50%', borderRadius: '50%',
@ -129,7 +130,7 @@ export const DashboardPage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ borderBottom: '1px solid var(--color-border-light)' }}> <tr style={{ borderBottom: '1px solid var(--color-border-light)' }}>
{['时间', '类型', '订单号', '金额', '状态'].map(h => ( {[t('dashboard_th_time'), t('dashboard_th_type'), t('dashboard_th_order'), t('dashboard_th_amount'), t('dashboard_th_status')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -141,11 +142,11 @@ export const DashboardPage: React.FC = () => {
</thead> </thead>
<tbody> <tbody>
{[ {[
{ time: '14:32:15', type: '购买', order: 'GNX-20260210-001234', amount: '$21.25', status: '完成' }, { time: '14:32:15', type: t('dashboard_type_purchase'), order: 'GNX-20260210-001234', amount: '$21.25', status: t('dashboard_status_completed') },
{ time: '14:31:58', type: '核销', order: 'GNX-20260210-001233', amount: '$50.00', status: '完成' }, { time: '14:31:58', type: t('dashboard_type_redeem'), order: 'GNX-20260210-001233', amount: '$50.00', status: t('dashboard_status_completed') },
{ time: '14:31:42', type: '转售', order: 'GNX-20260210-001232', amount: '$85.00', status: '完成' }, { time: '14:31:42', type: t('dashboard_type_resell'), order: 'GNX-20260210-001232', amount: '$85.00', status: t('dashboard_status_completed') },
{ time: '14:31:20', type: '购买', order: 'GNX-20260210-001231', amount: '$42.50', status: '处理中' }, { time: '14:31:20', type: t('dashboard_type_purchase'), order: 'GNX-20260210-001231', amount: '$42.50', status: t('dashboard_status_processing') },
{ time: '14:30:55', type: '转赠', order: 'GNX-20260210-001230', amount: '$30.00', status: '完成' }, { time: '14:30:55', type: t('dashboard_type_transfer'), order: 'GNX-20260210-001230', amount: '$30.00', status: t('dashboard_status_completed') },
].map((row, i) => ( ].map((row, i) => (
<tr key={i} style={{ borderBottom: '1px solid var(--color-border-light)' }}> <tr key={i} style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', padding: '10px 12px' }}>{row.time}</td> <td style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', padding: '10px 12px' }}>{row.time}</td>
@ -157,8 +158,8 @@ export const DashboardPage: React.FC = () => {
padding: '2px 8px', padding: '2px 8px',
borderRadius: 'var(--radius-full)', borderRadius: 'var(--radius-full)',
font: 'var(--text-caption)', font: 'var(--text-caption)',
background: row.status === '完成' ? 'var(--color-success-light)' : 'var(--color-warning-light)', background: row.status === t('dashboard_status_completed') ? 'var(--color-success-light)' : 'var(--color-warning-light)',
color: row.status === '完成' ? 'var(--color-success)' : 'var(--color-warning)', color: row.status === t('dashboard_status_completed') ? 'var(--color-success)' : 'var(--color-warning)',
}}> }}>
{row.status} {row.status}
</span> </span>
@ -176,13 +177,13 @@ export const DashboardPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('dashboard_system_health')}</div>
{[ {[
{ name: 'API服务', status: 'healthy', latency: '12ms' }, { name: t('dashboard_service_api'), status: 'healthy', latency: '12ms' },
{ name: '数据库', status: 'healthy', latency: '3ms' }, { name: t('dashboard_service_db'), status: 'healthy', latency: '3ms' },
{ name: 'Genex Chain', status: 'healthy', latency: '156ms' }, { name: 'Genex Chain', status: 'healthy', latency: '156ms' },
{ name: '缓存服务', status: 'healthy', latency: '1ms' }, { name: t('dashboard_service_cache'), status: 'healthy', latency: '1ms' },
{ name: '消息队列', status: 'warning', latency: '45ms' }, { name: t('dashboard_service_mq'), status: 'warning', latency: '45ms' },
].map(service => ( ].map(service => (
<div key={service.name} style={{ <div key={service.name} style={{
display: 'flex', display: 'flex',

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D8. * D8.
@ -9,7 +10,7 @@ import React from 'react';
interface Dispute { interface Dispute {
id: string; id: string;
type: '买方申诉' | '卖方申诉' | '退款申请'; type: string;
order: string; order: string;
plaintiff: string; plaintiff: string;
defendant: string; defendant: string;
@ -20,37 +21,37 @@ interface Dispute {
} }
const mockDisputes: Dispute[] = [ const mockDisputes: Dispute[] = [
{ id: 'DSP-001', type: '买方申诉', order: 'GNX-20260208-001200', plaintiff: 'U-012', defendant: 'U-045', amount: '$85.00', status: 'pending', createdAt: '2026-02-09', sla: '23h' }, { id: 'DSP-001', type: t('dispute_type_buyer'), order: 'GNX-20260208-001200', plaintiff: 'U-012', defendant: 'U-045', amount: '$85.00', status: 'pending', createdAt: '2026-02-09', sla: '23h' },
{ id: 'DSP-002', type: '退款申请', order: 'GNX-20260207-001150', plaintiff: 'U-023', defendant: '-', amount: '$42.50', status: 'processing', createdAt: '2026-02-08', sla: '6h' }, { id: 'DSP-002', type: t('dispute_type_refund'), order: 'GNX-20260207-001150', plaintiff: 'U-023', defendant: '-', amount: '$42.50', status: 'processing', createdAt: '2026-02-08', sla: '6h' },
{ id: 'DSP-003', type: '卖方申诉', order: 'GNX-20260206-001100', plaintiff: 'U-078', defendant: 'U-091', amount: '$120.00', status: 'pending', createdAt: '2026-02-07', sla: '47h' }, { id: 'DSP-003', type: t('dispute_type_seller'), order: 'GNX-20260206-001100', plaintiff: 'U-078', defendant: 'U-091', amount: '$120.00', status: 'pending', createdAt: '2026-02-07', sla: '47h' },
{ id: 'DSP-004', type: '买方申诉', order: 'GNX-20260205-001050', plaintiff: 'U-034', defendant: 'U-056', amount: '$30.00', status: 'resolved', createdAt: '2026-02-05', sla: '-' }, { id: 'DSP-004', type: t('dispute_type_buyer'), order: 'GNX-20260205-001050', plaintiff: 'U-034', defendant: 'U-056', amount: '$30.00', status: 'resolved', createdAt: '2026-02-05', sla: '-' },
{ id: 'DSP-005', type: '退款申请', order: 'GNX-20260204-001000', plaintiff: 'U-067', defendant: '-', amount: '$21.25', status: 'rejected', createdAt: '2026-02-04', sla: '-' }, { id: 'DSP-005', type: t('dispute_type_refund'), order: 'GNX-20260204-001000', plaintiff: 'U-067', defendant: '-', amount: '$21.25', status: 'rejected', createdAt: '2026-02-04', sla: '-' },
]; ];
const statusConfig: Record<string, { label: string; bg: string; color: string }> = { const statusConfig: Record<string, { label: string; bg: string; color: string }> = {
pending: { label: '待处理', bg: 'var(--color-warning-light)', color: 'var(--color-warning)' }, pending: { label: t('dispute_pending'), bg: 'var(--color-warning-light)', color: 'var(--color-warning)' },
processing: { label: '处理中', bg: 'var(--color-info-light)', color: 'var(--color-info)' }, processing: { label: t('dispute_processing'), bg: 'var(--color-info-light)', color: 'var(--color-info)' },
resolved: { label: '已解决', bg: 'var(--color-success-light)', color: 'var(--color-success)' }, resolved: { label: t('dispute_resolved'), bg: 'var(--color-success-light)', color: 'var(--color-success)' },
rejected: { label: '已驳回', bg: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }, rejected: { label: t('dispute_rejected'), bg: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' },
}; };
const typeConfig: Record<string, { bg: string; color: string }> = { const typeConfig: Record<string, { bg: string; color: string }> = {
'买方申诉': { bg: 'var(--color-error-light)', color: 'var(--color-error)' }, [t('dispute_type_buyer')]: { bg: 'var(--color-error-light)', color: 'var(--color-error)' },
'卖方申诉': { bg: 'var(--color-warning-light)', color: 'var(--color-warning)' }, [t('dispute_type_seller')]: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)' },
'退款申请': { bg: 'var(--color-info-light)', color: 'var(--color-info)' }, [t('dispute_type_refund')]: { bg: 'var(--color-info-light)', color: 'var(--color-info)' },
}; };
export const DisputePage: React.FC = () => { export const DisputePage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)' }}></h1> <h1 style={{ font: 'var(--text-h1)' }}>{t('dispute_title')}</h1>
<div style={{ display: 'flex', gap: 12 }}> <div style={{ display: 'flex', gap: 12 }}>
{/* Stats */} {/* Stats */}
{[ {[
{ label: '待处理', value: '3', color: 'var(--color-warning)' }, { label: t('dispute_pending'), value: '3', color: 'var(--color-warning)' },
{ label: '处理中', value: '1', color: 'var(--color-info)' }, { label: t('dispute_processing'), value: '1', color: 'var(--color-info)' },
{ label: '今日解决', value: '5', color: 'var(--color-success)' }, { label: t('dispute_resolved_today'), value: '5', color: 'var(--color-success)' },
].map(s => ( ].map(s => (
<div key={s.label} style={{ <div key={s.label} style={{
padding: '6px 14px', padding: '6px 14px',
@ -77,7 +78,7 @@ export const DisputePage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['工单号', '类型', '关联订单', '申诉方', '被诉方', '金额', '状态', '处理时效', '创建时间', '操作'].map(h => ( {[t('dispute_th_ticket_id'), t('dispute_th_type'), t('dispute_th_order'), t('dispute_th_plaintiff'), t('dispute_th_defendant'), t('dispute_th_amount'), t('dispute_th_status'), t('dispute_th_sla'), t('dispute_th_created'), t('actions')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -127,7 +128,7 @@ export const DisputePage: React.FC = () => {
borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer',
font: 'var(--text-caption)', color: 'var(--color-primary)', font: 'var(--text-caption)', color: 'var(--color-primary)',
}}> }}>
{d.status === 'pending' || d.status === 'processing' ? '处理' : '查看'} {d.status === 'pending' || d.status === 'processing' ? t('process') : t('view')}
</button> </button>
</td> </td>
</tr> </tr>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D3. - * D3. -
@ -7,23 +8,23 @@ import React from 'react';
*/ */
const financeStats = [ const financeStats = [
{ label: '平台手续费收入', value: '$234,567', period: '本月', color: 'var(--color-success)' }, { label: t('finance_platform_fee'), value: '$234,567', period: t('finance_period_month'), color: 'var(--color-success)' },
{ label: '待结算给发行方', value: '$1,456,000', period: '累计', color: 'var(--color-warning)' }, { label: t('finance_pending_settlement'), value: '$1,456,000', period: t('finance_period_cumulative'), color: 'var(--color-warning)' },
{ label: '消费者退款', value: '$12,340', period: '本月', color: 'var(--color-error)' }, { label: t('finance_consumer_refund'), value: '$12,340', period: t('finance_period_month'), color: 'var(--color-error)' },
{ label: '资金池余额', value: '$8,234,567', period: '实时', color: 'var(--color-primary)' }, { label: t('finance_pool_balance'), value: '$8,234,567', period: t('finance_period_realtime'), color: 'var(--color-primary)' },
]; ];
const recentSettlements = [ const recentSettlements = [
{ issuer: 'Starbucks', amount: '$45,200', status: '已结算', time: '2026-02-10 14:00' }, { issuer: 'Starbucks', amount: '$45,200', status: t('finance_status_settled'), time: '2026-02-10 14:00' },
{ issuer: 'Amazon', amount: '$128,000', status: '处理中', time: '2026-02-10 12:00' }, { issuer: 'Amazon', amount: '$128,000', status: t('finance_status_processing'), time: '2026-02-10 12:00' },
{ issuer: 'Nike', amount: '$23,500', status: '待结算', time: '2026-02-09' }, { issuer: 'Nike', amount: '$23,500', status: t('finance_status_pending'), time: '2026-02-09' },
{ issuer: 'Walmart', amount: '$67,800', status: '已结算', time: '2026-02-08' }, { issuer: 'Walmart', amount: '$67,800', status: t('finance_status_settled'), time: '2026-02-08' },
]; ];
export const FinanceManagementPage: React.FC = () => { export const FinanceManagementPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>{t('finance_title')}</h1>
{/* Stats */} {/* Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
@ -45,10 +46,10 @@ export const FinanceManagementPage: React.FC = () => {
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, border: '1px solid var(--color-border-light)', padding: 20,
}}> }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('finance_settlement_queue')}</h2>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr>{['发行方', '金额', '状态', '时间'].map(h => ( <tr>{[t('finance_th_issuer'), t('finance_th_amount'), t('finance_th_status'), t('finance_th_time')].map(h => (
<th key={h} style={{ padding: '8px 0', textAlign: 'left', font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', borderBottom: '1px solid var(--color-border-light)' }}>{h}</th> <th key={h} style={{ padding: '8px 0', textAlign: 'left', font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', borderBottom: '1px solid var(--color-border-light)' }}>{h}</th>
))}</tr> ))}</tr>
</thead> </thead>
@ -60,8 +61,8 @@ export const FinanceManagementPage: React.FC = () => {
<td style={{ padding: '10px 0' }}> <td style={{ padding: '10px 0' }}>
<span style={{ <span style={{
padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600, padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600,
background: s.status === '已结算' ? 'var(--color-success-light)' : s.status === '处理中' ? 'var(--color-info-light)' : 'var(--color-warning-light)', background: s.status === t('finance_status_settled') ? 'var(--color-success-light)' : s.status === t('finance_status_processing') ? 'var(--color-info-light)' : 'var(--color-warning-light)',
color: s.status === '已结算' ? 'var(--color-success)' : s.status === '处理中' ? 'var(--color-info)' : 'var(--color-warning)', color: s.status === t('finance_status_settled') ? 'var(--color-success)' : s.status === t('finance_status_processing') ? 'var(--color-info)' : 'var(--color-warning)',
}}>{s.status}</span> }}>{s.status}</span>
</td> </td>
<td style={{ padding: '10px 0', font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>{s.time}</td> <td style={{ padding: '10px 0', font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>{s.time}</td>
@ -76,7 +77,7 @@ export const FinanceManagementPage: React.FC = () => {
background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', background: 'var(--color-surface)', borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)', padding: 20, border: '1px solid var(--color-border-light)', padding: 20,
}}> }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('finance_revenue_trend')}</h2>
<div style={{ <div style={{
height: 240, background: 'var(--color-gray-50)', borderRadius: 'var(--radius-sm)', height: 240, background: 'var(--color-gray-50)', borderRadius: 'var(--radius-sm)',
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D8. - * D8. -
@ -7,16 +8,16 @@ import React from 'react';
*/ */
const protectionStats = [ const protectionStats = [
{ label: '消费者保护基金', value: '$2,345,678', color: 'var(--color-success)' }, { label: t('insurance_protection_fund'), value: '$2,345,678', color: 'var(--color-success)' },
{ label: '本月赔付', value: '$12,340', color: 'var(--color-warning)' }, { label: t('insurance_monthly_payout'), value: '$12,340', color: 'var(--color-warning)' },
{ label: '赔付率', value: '0.08%', color: 'var(--color-info)' }, { label: t('insurance_payout_rate'), value: '0.08%', color: 'var(--color-info)' },
{ label: 'IPO准备度', value: '72%', color: 'var(--color-primary)' }, { label: t('insurance_ipo_readiness'), value: '72%', color: 'var(--color-primary)' },
]; ];
const recentClaims = [ const recentClaims = [
{ id: 'CLM-001', user: 'User#12345', reason: '发行方破产', amount: '$250', status: '已赔付', date: '2026-02-08' }, { id: 'CLM-001', user: 'User#12345', reason: '发行方破产', amount: '$250', status: t('insurance_status_paid'), date: '2026-02-08' },
{ id: 'CLM-002', user: 'User#23456', reason: '券核销失败', amount: '$100', status: '处理中', date: '2026-02-09' }, { id: 'CLM-002', user: 'User#23456', reason: '券核销失败', amount: '$100', status: t('insurance_status_processing'), date: '2026-02-09' },
{ id: 'CLM-003', user: 'User#34567', reason: '重复扣款', amount: '$42.50', status: '已赔付', date: '2026-02-07' }, { id: 'CLM-003', user: 'User#34567', reason: '重复扣款', amount: '$42.50', status: t('insurance_status_paid'), date: '2026-02-07' },
]; ];
const ipoChecklist = [ const ipoChecklist = [
@ -32,7 +33,7 @@ const ipoChecklist = [
export const InsurancePage: React.FC = () => { export const InsurancePage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>{t('insurance_title')}</h1>
{/* Stats */} {/* Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
@ -50,10 +51,10 @@ export const InsurancePage: React.FC = () => {
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
{/* Claims */} {/* Claims */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('insurance_recent_claims')}</h2>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr>{['编号', '用户', '原因', '金额', '状态'].map(h => ( <tr>{[t('insurance_th_id'), t('insurance_th_user'), t('insurance_th_reason'), t('insurance_th_amount'), t('insurance_th_status')].map(h => (
<th key={h} style={{ padding: '8px 0', textAlign: 'left', font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', borderBottom: '1px solid var(--color-border-light)' }}>{h}</th> <th key={h} style={{ padding: '8px 0', textAlign: 'left', font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', borderBottom: '1px solid var(--color-border-light)' }}>{h}</th>
))}</tr> ))}</tr>
</thead> </thead>
@ -67,8 +68,8 @@ export const InsurancePage: React.FC = () => {
<td style={{ padding: '10px 0' }}> <td style={{ padding: '10px 0' }}>
<span style={{ <span style={{
padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600, padding: '2px 8px', borderRadius: 'var(--radius-full)', font: 'var(--text-caption)', fontWeight: 600,
background: c.status === '已赔付' ? 'var(--color-success-light)' : 'var(--color-warning-light)', background: c.status === t('insurance_status_paid') ? 'var(--color-success-light)' : 'var(--color-warning-light)',
color: c.status === '已赔付' ? 'var(--color-success)' : 'var(--color-warning)', color: c.status === t('insurance_status_paid') ? 'var(--color-success)' : 'var(--color-warning)',
}}>{c.status}</span> }}>{c.status}</span>
</td> </td>
</tr> </tr>
@ -79,7 +80,7 @@ export const InsurancePage: React.FC = () => {
{/* IPO Readiness */} {/* IPO Readiness */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>IPO准备度检查清单</h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('insurance_ipo_checklist')}</h2>
{ipoChecklist.map((item, i) => ( {ipoChecklist.map((item, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}> <div key={i} style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid var(--color-border-light)' }}>
<div style={{ <div style={{
@ -94,14 +95,14 @@ export const InsurancePage: React.FC = () => {
font: 'var(--text-caption)', font: 'var(--text-caption)',
color: item.status === 'done' ? 'var(--color-success)' : item.status === 'progress' ? 'var(--color-warning)' : 'var(--color-text-tertiary)', color: item.status === 'done' ? 'var(--color-success)' : item.status === 'progress' ? 'var(--color-warning)' : 'var(--color-text-tertiary)',
}}> }}>
{item.status === 'done' ? '已完成' : item.status === 'progress' ? '进行中' : '待开始'} {item.status === 'done' ? t('completed') : item.status === 'progress' ? t('in_progress') : t('pending')}
</span> </span>
</div> </div>
))} ))}
{/* Progress Bar */} {/* Progress Bar */}
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}></span> <span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}>{t('overall_progress')}</span>
<span style={{ font: 'var(--text-label-sm)', color: 'var(--color-primary)' }}>72%</span> <span style={{ font: 'var(--text-label-sm)', color: 'var(--color-primary)' }}>72%</span>
</div> </div>
<div style={{ height: 8, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden' }}> <div style={{ height: 8, background: 'var(--color-gray-100)', borderRadius: 'var(--radius-full)', overflow: 'hidden' }}>

View File

@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { t } from '@/i18n/locales';
/** /**
* D2. * D2.
@ -51,14 +52,14 @@ export const IssuerManagementPage: React.FC = () => {
}; };
const statusLabel = (status: string) => { const statusLabel = (status: string) => {
const map: Record<string, string> = { pending: '待审核', approved: '已通过', rejected: '已驳回' }; const map: Record<string, string> = { pending: t('issuer_onboarding_pending'), approved: t('issuer_onboarding_approved'), rejected: t('issuer_onboarding_rejected') };
return map[status] || status; return map[status] || status;
}; };
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)' }}></h1> <h1 style={{ font: 'var(--text-h1)' }}>{t('issuer_management_title')}</h1>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
<button style={{ <button style={{
padding: '6px 16px', padding: '6px 16px',
@ -69,29 +70,29 @@ export const IssuerManagementPage: React.FC = () => {
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}}> }}>
AI {t('issuer_ai_pre_review')}
</button> </button>
</div> </div>
</div> </div>
{/* Tabs */} {/* Tabs */}
<div style={{ display: 'flex', gap: 4, marginBottom: 20 }}> <div style={{ display: 'flex', gap: 4, marginBottom: 20 }}>
{(['all', 'pending', 'approved', 'rejected'] as const).map(t => ( {(['all', 'pending', 'approved', 'rejected'] as const).map(tabKey => (
<button <button
key={t} key={tabKey}
onClick={() => setTab(t)} onClick={() => setTab(tabKey)}
style={{ style={{
padding: '8px 16px', padding: '8px 16px',
border: 'none', border: 'none',
borderRadius: 'var(--radius-full)', borderRadius: 'var(--radius-full)',
background: tab === t ? 'var(--color-primary)' : 'var(--color-gray-50)', background: tab === tabKey ? 'var(--color-primary)' : 'var(--color-gray-50)',
color: tab === t ? 'white' : 'var(--color-text-secondary)', color: tab === tabKey ? 'white' : 'var(--color-text-secondary)',
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}} }}
> >
{t === 'all' ? '全部' : statusLabel(t)} {tabKey === 'all' ? t('all') : statusLabel(tabKey)}
{t === 'pending' && ( {tabKey === 'pending' && (
<span style={{ <span style={{
marginLeft: 4, padding: '0 5px', marginLeft: 4, padding: '0 5px',
background: 'var(--color-error)', background: 'var(--color-error)',
@ -116,7 +117,7 @@ export const IssuerManagementPage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)', borderBottom: '1px solid var(--color-border)' }}> <tr style={{ background: 'var(--color-gray-50)', borderBottom: '1px solid var(--color-border)' }}>
{['ID', '企业名称', '信用评级', '状态', '提交时间', '券数量', '总发行额', '操作'].map(h => ( {['ID', t('issuer_company_name'), t('issuer_credit_rating'), t('status'), t('issuer_submit_time'), t('issuer_coupon_count'), t('issuer_total_volume'), t('actions')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -177,7 +178,7 @@ export const IssuerManagementPage: React.FC = () => {
font: 'var(--text-caption)', font: 'var(--text-caption)',
color: 'var(--color-primary)', color: 'var(--color-primary)',
}}> }}>
{t('details')}
</button> </button>
{issuer.status === 'pending' && ( {issuer.status === 'pending' && (
<button style={{ <button style={{
@ -190,7 +191,7 @@ export const IssuerManagementPage: React.FC = () => {
font: 'var(--text-caption)', font: 'var(--text-caption)',
color: 'white', color: 'white',
}}> }}>
{t('review')}
</button> </button>
)} )}
</td> </td>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D6. - * D6. -
@ -7,10 +8,10 @@ import React from 'react';
*/ */
const redemptionStats = [ const redemptionStats = [
{ label: '今日核销', value: '1,234', change: '+15%', color: 'var(--color-success)' }, { label: t('merchant_today_redemption'), value: '1,234', change: '+15%', color: 'var(--color-success)' },
{ label: '今日核销金额', value: '$45,600', change: '+8%', color: 'var(--color-primary)' }, { label: t('merchant_today_amount'), value: '$45,600', change: '+8%', color: 'var(--color-primary)' },
{ label: '活跃门店', value: '89', change: '+3', color: 'var(--color-info)' }, { label: t('merchant_active_stores'), value: '89', change: '+3', color: 'var(--color-info)' },
{ label: '异常核销', value: '2', change: '需审核', color: 'var(--color-error)' }, { label: t('merchant_abnormal_redemption'), value: '2', change: t('merchant_need_review'), color: 'var(--color-error)' },
]; ];
const topStores = [ const topStores = [
@ -24,7 +25,7 @@ const topStores = [
export const MerchantRedemptionPage: React.FC = () => { export const MerchantRedemptionPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)', marginBottom: 24 }}>{t('merchant_title')}</h1>
{/* Stats */} {/* Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
@ -43,7 +44,7 @@ export const MerchantRedemptionPage: React.FC = () => {
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
{/* Top Stores */} {/* Top Stores */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('merchant_store_ranking')}</h2>
{topStores.map(s => ( {topStores.map(s => (
<div key={s.rank} style={{ <div key={s.rank} style={{
display: 'flex', alignItems: 'center', padding: '10px 0', display: 'flex', alignItems: 'center', padding: '10px 0',
@ -55,7 +56,7 @@ export const MerchantRedemptionPage: React.FC = () => {
color: s.rank <= 3 ? 'white' : 'var(--color-text-tertiary)', font: 'var(--text-caption)', fontWeight: 700, color: s.rank <= 3 ? 'white' : 'var(--color-text-tertiary)', font: 'var(--text-caption)', fontWeight: 700,
}}>{s.rank}</span> }}>{s.rank}</span>
<span style={{ flex: 1, marginLeft: 12, font: 'var(--text-body)' }}>{s.store}</span> <span style={{ flex: 1, marginLeft: 12, font: 'var(--text-body)' }}>{s.store}</span>
<span style={{ font: 'var(--text-label)', color: 'var(--color-primary)', marginRight: 16 }}>{s.count}</span> <span style={{ font: 'var(--text-label)', color: 'var(--color-primary)', marginRight: 16 }}>{s.count}{t('merchant_unit_count')}</span>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}>{s.amount}</span> <span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}>{s.amount}</span>
</div> </div>
))} ))}
@ -63,7 +64,7 @@ export const MerchantRedemptionPage: React.FC = () => {
{/* Realtime Feed */} {/* Realtime Feed */}
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}> <div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-light)', padding: 20 }}>
<h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}></h2> <h2 style={{ font: 'var(--text-h2)', marginBottom: 16 }}>{t('merchant_realtime_feed')}</h2>
{[ {[
{ store: 'Starbucks 徐汇店', coupon: '¥25 礼品卡', time: '刚刚' }, { store: 'Starbucks 徐汇店', coupon: '¥25 礼品卡', time: '刚刚' },
{ store: 'Nike 南京西路店', coupon: '¥80 运动券', time: '1分钟前' }, { store: 'Nike 南京西路店', coupon: '¥80 运动券', time: '1分钟前' },

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D5. - * D5. -
@ -9,50 +10,50 @@ import React from 'react';
const reportCategories = [ const reportCategories = [
{ {
title: '运营报表', title: t('reports_operations'),
icon: '📊', icon: '📊',
reports: [ reports: [
{ name: '日度运营报表', desc: '交易量/金额/用户/核销率', status: '已生成', date: '2026-02-10' }, { name: '日度运营报表', desc: '交易量/金额/用户/核销率', status: t('reports_status_generated'), date: '2026-02-10' },
{ name: '周度运营报表', desc: '周趋势分析', status: '已生成', date: '2026-02-09' }, { name: '周度运营报表', desc: '周趋势分析', status: t('reports_status_generated'), date: '2026-02-09' },
{ name: '月度运营报表', desc: '月度综合分析', status: '已生成', date: '2026-01-31' }, { name: '月度运营报表', desc: '月度综合分析', status: t('reports_status_generated'), date: '2026-01-31' },
], ],
}, },
{ {
title: '合规报表', title: t('reports_compliance'),
icon: '📋', icon: '📋',
reports: [ reports: [
{ name: 'SAR可疑活动报告', desc: '本月可疑交易汇总', status: '待审核', date: '2026-02-10' }, { name: 'SAR可疑活动报告', desc: '本月可疑交易汇总', status: t('reports_status_pending_review'), date: '2026-02-10' },
{ name: 'CTR大额交易报告', desc: '>$10,000交易申报', status: '已提交', date: '2026-02-10' }, { name: 'CTR大额交易报告', desc: '>$10,000交易申报', status: t('reports_status_submitted'), date: '2026-02-10' },
{ name: 'OFAC筛查报告', desc: '制裁名单筛查结果', status: '已生成', date: '2026-02-09' }, { name: 'OFAC筛查报告', desc: '制裁名单筛查结果', status: t('reports_status_generated'), date: '2026-02-09' },
], ],
}, },
{ {
title: '财务报表', title: t('reports_financial'),
icon: '💰', icon: '💰',
reports: [ reports: [
{ name: '发行方结算报表', desc: '各发行方结算明细', status: '已生成', date: '2026-02-10' }, { name: '发行方结算报表', desc: '各发行方结算明细', status: t('reports_status_generated'), date: '2026-02-10' },
{ name: '平台收入报表', desc: '手续费/Breakage收入', status: '已生成', date: '2026-01-31' }, { name: '平台收入报表', desc: '手续费/Breakage收入', status: t('reports_status_generated'), date: '2026-01-31' },
{ name: '税务合规报表', desc: '1099-K/消费税汇总', status: '待生成', date: '' }, { name: '税务合规报表', desc: '1099-K/消费税汇总', status: t('reports_status_pending_generate'), date: '' },
], ],
}, },
{ {
title: '审计报表', title: t('reports_audit'),
icon: '🔍', icon: '🔍',
reports: [ reports: [
{ name: 'SOX合规检查', desc: '内部控制审计', status: '已通过', date: '2026-01-15' }, { name: 'SOX合规检查', desc: '内部控制审计', status: t('reports_status_passed'), date: '2026-01-15' },
{ name: 'SEC Filing', desc: '证券类披露(预留)', status: 'N/A', date: '' }, { name: 'SEC Filing', desc: '证券类披露(预留)', status: 'N/A', date: '' },
{ name: '操作审计日志', desc: '管理员操作记录', status: '已生成', date: '2026-02-10' }, { name: '操作审计日志', desc: '管理员操作记录', status: t('reports_status_generated'), date: '2026-02-10' },
], ],
}, },
]; ];
const statusStyle = (status: string): React.CSSProperties => { const statusStyle = (status: string): React.CSSProperties => {
const map: Record<string, { bg: string; color: string }> = { const map: Record<string, { bg: string; color: string }> = {
'已生成': { bg: 'var(--color-success-light)', color: 'var(--color-success)' }, [t('reports_status_generated')]: { bg: 'var(--color-success-light)', color: 'var(--color-success)' },
'已提交': { bg: 'var(--color-info-light)', color: 'var(--color-info)' }, [t('reports_status_submitted')]: { bg: 'var(--color-info-light)', color: 'var(--color-info)' },
'已通过': { bg: 'var(--color-success-light)', color: 'var(--color-success)' }, [t('reports_status_passed')]: { bg: 'var(--color-success-light)', color: 'var(--color-success)' },
'待审核': { bg: 'var(--color-warning-light)', color: 'var(--color-warning)' }, [t('reports_status_pending_review')]: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)' },
'待生成': { bg: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }, [t('reports_status_pending_generate')]: { bg: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' },
'N/A': { bg: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' }, 'N/A': { bg: 'var(--color-gray-100)', color: 'var(--color-text-tertiary)' },
}; };
const s = map[status] || map['N/A']; const s = map[status] || map['N/A'];
@ -63,11 +64,11 @@ export const ReportsPage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}></h1> <h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}>{t('reports_title')}</h1>
<button style={{ <button style={{
padding: '8px 16px', border: 'none', borderRadius: 'var(--radius-sm)', padding: '8px 16px', border: 'none', borderRadius: 'var(--radius-sm)',
background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label)', background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label)',
}}></button> }}>{t('custom_export')}</button>
</div> </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
@ -91,11 +92,11 @@ export const ReportsPage: React.FC = () => {
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<span style={statusStyle(r.status)}>{r.status}</span> <span style={statusStyle(r.status)}>{r.status}</span>
{r.date && <span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{r.date}</span>} {r.date && <span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{r.date}</span>}
{r.status !== 'N/A' && r.status !== '待生成' && ( {r.status !== 'N/A' && r.status !== t('reports_status_pending_generate') && (
<button style={{ <button style={{
padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)',
background: 'transparent', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)', background: 'transparent', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)',
}}></button> }}>{t('download')}</button>
)} )}
</div> </div>
</div> </div>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D5. * D5.
@ -10,7 +11,7 @@ export const RiskCenterPage: React.FC = () => {
return ( return (
<div> <div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)' }}></h1> <h1 style={{ font: 'var(--text-h1)' }}>{t('risk_title')}</h1>
<button style={{ <button style={{
padding: '8px 16px', padding: '8px 16px',
border: '1px solid var(--color-primary)', border: '1px solid var(--color-primary)',
@ -20,17 +21,17 @@ export const RiskCenterPage: React.FC = () => {
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}}> }}>
AI {t('risk_ai_warning')}
</button> </button>
</div> </div>
{/* Risk Stats */} {/* Risk Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
{[ {[
{ label: '风险事件', value: '23', color: 'var(--color-error)', bg: 'var(--color-error-light)' }, { label: t('risk_events'), value: '23', color: 'var(--color-error)', bg: 'var(--color-error-light)' },
{ label: '可疑交易', value: '15', color: 'var(--color-warning)', bg: 'var(--color-warning-light)' }, { label: t('risk_suspicious_trades'), value: '15', color: 'var(--color-warning)', bg: 'var(--color-warning-light)' },
{ label: '冻结账户', value: '3', color: 'var(--color-info)', bg: 'var(--color-info-light)' }, { label: t('risk_frozen_accounts'), value: '3', color: 'var(--color-info)', bg: 'var(--color-info-light)' },
{ label: 'OFAC命中', value: '0', color: 'var(--color-success)', bg: 'var(--color-success-light)' }, { label: t('risk_ofac_hits'), value: '0', color: 'var(--color-success)', bg: 'var(--color-success-light)' },
].map(s => ( ].map(s => (
<div key={s.label} style={{ <div key={s.label} style={{
background: s.bg, background: s.bg,
@ -52,7 +53,7 @@ export const RiskCenterPage: React.FC = () => {
marginBottom: 24, marginBottom: 24,
}}> }}>
<div style={{ font: 'var(--text-label)', color: 'var(--color-primary)', marginBottom: 12 }}> <div style={{ font: 'var(--text-label)', color: 'var(--color-primary)', marginBottom: 12 }}>
🤖 AI 🤖 {t('risk_ai_warning')}
</div> </div>
{[ {[
'检测到异常模式用户U-045在30分钟内完成12笔交易总金额$4,560建议人工审核', '检测到异常模式用户U-045在30分钟内完成12笔交易总金额$4,560建议人工审核',
@ -79,7 +80,7 @@ export const RiskCenterPage: React.FC = () => {
font: 'var(--text-caption)', font: 'var(--text-caption)',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
marginLeft: 12, marginLeft: 12,
}}></button> }}>{t('process')}</button>
</div> </div>
))} ))}
</div> </div>
@ -92,12 +93,12 @@ export const RiskCenterPage: React.FC = () => {
overflow: 'hidden', overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}>
{t('risk_suspicious_trades')}
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['交易ID', '用户', '异常类型', '金额', '时间', '风险评分', '操作'].map(h => ( {[t('risk_th_trade_id'), t('risk_th_user'), t('risk_th_anomaly_type'), t('risk_th_amount'), t('risk_th_time'), t('risk_th_risk_score'), t('actions')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -109,10 +110,10 @@ export const RiskCenterPage: React.FC = () => {
</thead> </thead>
<tbody> <tbody>
{[ {[
{ id: 'TXN-8901', user: 'U-045', type: '高频交易', amount: '$4,560', time: '14:15', score: 87 }, { id: 'TXN-8901', user: 'U-045', type: t('risk_type_high_freq'), amount: '$4,560', time: '14:15', score: 87 },
{ id: 'TXN-8900', user: 'U-078', type: '大额单笔', amount: '$8,900', time: '13:45', score: 72 }, { id: 'TXN-8900', user: 'U-078', type: t('risk_type_large_single'), amount: '$8,900', time: '13:45', score: 72 },
{ id: 'TXN-8899', user: 'U-091', type: '关联账户', amount: '$3,200', time: '12:30', score: 65 }, { id: 'TXN-8899', user: 'U-091', type: t('risk_type_related_account'), amount: '$3,200', time: '12:30', score: 65 },
{ id: 'TXN-8898', user: 'U-023', type: '异常IP', amount: '$1,500', time: '11:20', score: 58 }, { id: 'TXN-8898', user: 'U-023', type: t('risk_type_abnormal_ip'), amount: '$1,500', time: '11:20', score: 58 },
].map(tx => ( ].map(tx => (
<tr key={tx.id} style={{ borderBottom: '1px solid var(--color-border-light)' }}> <tr key={tx.id} style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ font: 'var(--text-body-sm)', fontFamily: 'var(--font-family-mono)', padding: '10px 14px' }}>{tx.id}</td> <td style={{ font: 'var(--text-body-sm)', fontFamily: 'var(--font-family-mono)', padding: '10px 14px' }}>{tx.id}</td>
@ -149,8 +150,8 @@ export const RiskCenterPage: React.FC = () => {
</div> </div>
</td> </td>
<td style={{ padding: '10px 14px', display: 'flex', gap: 4 }}> <td style={{ padding: '10px 14px', display: 'flex', gap: 4 }}>
<button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)' }}></button> <button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)' }}>{t('mark')}</button>
<button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-error)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}></button> <button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-error)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}>{t('user_freeze')}</button>
<button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-warning)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}>SAR</button> <button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-warning)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}>SAR</button>
</td> </td>
</tr> </tr>

View File

@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { t } from '@/i18n/locales';
/** /**
* D7. * D7.
@ -13,29 +14,29 @@ export const SystemManagementPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', marginBottom: 24 }}></h1> <h1 style={{ font: 'var(--text-h1)', marginBottom: 24 }}>{t('system_title')}</h1>
{/* Tabs */} {/* Tabs */}
<div style={{ display: 'flex', gap: 4, marginBottom: 20, borderBottom: '1px solid var(--color-border-light)' }}> <div style={{ display: 'flex', gap: 4, marginBottom: 20, borderBottom: '1px solid var(--color-border-light)' }}>
{[ {[
{ key: 'admins', label: '管理员账号' }, { key: 'admins', label: t('system_tab_admins') },
{ key: 'config', label: '系统配置' }, { key: 'config', label: t('system_tab_config') },
{ key: 'contracts', label: '合约管理' }, { key: 'contracts', label: t('system_tab_contracts') },
{ key: 'monitor', label: '系统监控' }, { key: 'monitor', label: t('system_tab_monitor') },
].map(t => ( ].map(tab => (
<button <button
key={t.key} key={tab.key}
onClick={() => setActiveTab(t.key as typeof activeTab)} onClick={() => setActiveTab(tab.key as typeof activeTab)}
style={{ style={{
padding: '10px 16px', padding: '10px 16px',
border: 'none', border: 'none',
borderBottom: activeTab === t.key ? '2px solid var(--color-primary)' : '2px solid transparent', borderBottom: activeTab === tab.key ? '2px solid var(--color-primary)' : '2px solid transparent',
background: 'none', background: 'none',
color: activeTab === t.key ? 'var(--color-primary)' : 'var(--color-text-tertiary)', color: activeTab === tab.key ? 'var(--color-primary)' : 'var(--color-text-tertiary)',
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-label)', font: 'var(--text-label)',
}} }}
>{t.label}</button> >{tab.label}</button>
))} ))}
</div> </div>
@ -48,16 +49,16 @@ export const SystemManagementPage: React.FC = () => {
overflow: 'hidden', overflow: 'hidden',
}}> }}>
<div style={{ padding: '14px 20px', borderBottom: '1px solid var(--color-border-light)', display: 'flex', justifyContent: 'space-between' }}> <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--color-border-light)', display: 'flex', justifyContent: 'space-between' }}>
<span style={{ font: 'var(--text-h3)' }}></span> <span style={{ font: 'var(--text-h3)' }}>{t('system_admin_list')}</span>
<button style={{ <button style={{
padding: '6px 14px', border: 'none', borderRadius: 'var(--radius-sm)', padding: '6px 14px', border: 'none', borderRadius: 'var(--radius-sm)',
background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label-sm)', background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label-sm)',
}}>+ </button> }}>{t('system_add_admin')}</button>
</div> </div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['账号', '姓名', '角色', '最后登录', '状态', '操作'].map(h => ( {[t('system_th_account'), t('system_th_name'), t('system_th_role'), t('system_th_last_login'), t('system_th_status'), t('actions')].map(h => (
<th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th> <th key={h} style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px', textAlign: 'left' }}>{h}</th>
))} ))}
</tr> </tr>
@ -87,7 +88,7 @@ export const SystemManagementPage: React.FC = () => {
}} /> }} />
</td> </td>
<td style={{ padding: '10px 14px' }}> <td style={{ padding: '10px 14px' }}>
<button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)' }}></button> <button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)' }}>{t('edit')}</button>
</td> </td>
</tr> </tr>
))} ))}
@ -100,10 +101,10 @@ export const SystemManagementPage: React.FC = () => {
{activeTab === 'config' && ( {activeTab === 'config' && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16 }}>
{[ {[
{ title: '手续费率设置', items: [{ label: '一级市场手续费', value: '2.5%' }, { label: '二级市场手续费', value: '3.0%' }, { label: '提现手续费', value: '1.0%' }] }, { title: t('system_fee_config'), items: [{ label: '一级市场手续费', value: '2.5%' }, { label: '二级市场手续费', value: '3.0%' }, { label: '提现手续费', value: '1.0%' }] },
{ title: 'KYC阈值配置', items: [{ label: 'L0每日限额', value: '$100' }, { label: 'L1每日限额', value: '$1,000' }, { label: 'L2每日限额', value: '$10,000' }] }, { title: t('system_kyc_config'), items: [{ label: 'L0每日限额', value: '$100' }, { label: 'L1每日限额', value: '$1,000' }, { label: 'L2每日限额', value: '$10,000' }] },
{ title: '交易限额配置', items: [{ label: '单笔最大金额', value: '$50,000' }, { label: '每日最大金额', value: '$100,000' }, { label: '大额交易阈值', value: '$10,000' }] }, { title: t('system_trade_limit_config'), items: [{ label: '单笔最大金额', value: '$50,000' }, { label: '每日最大金额', value: '$100,000' }, { label: '大额交易阈值', value: '$10,000' }] },
{ title: '系统参数', items: [{ label: 'Utility Track价格上限', value: '≤面值' }, { label: '最大转售次数', value: '5次' }, { label: 'Breakage阈值', value: '3年' }] }, { title: t('system_params'), items: [{ label: 'Utility Track价格上限', value: '≤面值' }, { label: '最大转售次数', value: '5次' }, { label: 'Breakage阈值', value: '3年' }] },
].map(section => ( ].map(section => (
<div key={section.title} style={{ <div key={section.title} style={{
background: 'var(--color-surface)', background: 'var(--color-surface)',
@ -117,7 +118,7 @@ export const SystemManagementPage: React.FC = () => {
padding: '4px 10px', border: '1px solid var(--color-border)', padding: '4px 10px', border: '1px solid var(--color-border)',
borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer',
font: 'var(--text-caption)', color: 'var(--color-primary)', font: 'var(--text-caption)', color: 'var(--color-primary)',
}}></button> }}>{t('edit')}</button>
</div> </div>
{section.items.map((item, i) => ( {section.items.map((item, i) => (
<div key={item.label} style={{ <div key={item.label} style={{
@ -143,12 +144,12 @@ export const SystemManagementPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('system_contract_status')}</div>
{[ {[
{ name: 'CouponNFT', address: '0x1234...abcd', version: 'v1.2.0', status: '运行中' }, { name: 'CouponNFT', address: '0x1234...abcd', version: 'v1.2.0', status: t('system_running') },
{ name: 'Settlement', address: '0x5678...efgh', version: 'v1.1.0', status: '运行中' }, { name: 'Settlement', address: '0x5678...efgh', version: 'v1.1.0', status: t('system_running') },
{ name: 'Marketplace', address: '0x9abc...ijkl', version: 'v1.0.0', status: '运行中' }, { name: 'Marketplace', address: '0x9abc...ijkl', version: 'v1.0.0', status: t('system_running') },
{ name: 'Oracle', address: '0xdef0...mnop', version: 'v1.0.0', status: '运行中' }, { name: 'Oracle', address: '0xdef0...mnop', version: 'v1.0.0', status: t('system_running') },
].map(c => ( ].map(c => (
<div key={c.name} style={{ <div key={c.name} style={{
display: 'flex', alignItems: 'center', padding: '14px 0', display: 'flex', alignItems: 'center', padding: '14px 0',
@ -179,7 +180,7 @@ export const SystemManagementPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('system_health_check')}</div>
{[ {[
{ name: 'API Gateway', status: 'healthy', cpu: '23%', mem: '45%' }, { name: 'API Gateway', status: 'healthy', cpu: '23%', mem: '45%' },
{ name: 'Auth Service', status: 'healthy', cpu: '12%', mem: '34%' }, { name: 'Auth Service', status: 'healthy', cpu: '12%', mem: '34%' },
@ -206,7 +207,7 @@ export const SystemManagementPage: React.FC = () => {
border: '1px solid var(--color-border-light)', border: '1px solid var(--color-border-light)',
padding: 20, padding: 20,
}}> }}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>API </div> <div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>{t('system_api_response')}</div>
<div style={{ <div style={{
height: 240, height: 240,
background: 'var(--color-gray-50)', background: 'var(--color-gray-50)',

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n/locales';
/** /**
* D4. * D4.
@ -9,15 +10,15 @@ import React from 'react';
export const TradingMonitorPage: React.FC = () => { export const TradingMonitorPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', marginBottom: 24 }}></h1> <h1 style={{ font: 'var(--text-h1)', marginBottom: 24 }}>{t('trading_title')}</h1>
{/* Stats Row */} {/* Stats Row */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
{[ {[
{ label: '今日交易量', value: '2,456', color: 'var(--color-primary)' }, { label: t('trading_today_volume'), value: '2,456', color: 'var(--color-primary)' },
{ label: '今日交易额', value: '$156,789', color: 'var(--color-success)' }, { label: t('trading_today_amount'), value: '$156,789', color: 'var(--color-success)' },
{ label: '平均折扣率', value: '82.3%', color: 'var(--color-info)' }, { label: t('trading_avg_discount'), value: '82.3%', color: 'var(--color-info)' },
{ label: '大额交易', value: '12', color: 'var(--color-warning)' }, { label: t('trading_large_trades'), value: '12', color: 'var(--color-warning)' },
].map(s => ( ].map(s => (
<div key={s.label} style={{ <div key={s.label} style={{
background: 'var(--color-surface)', background: 'var(--color-surface)',
@ -40,7 +41,7 @@ export const TradingMonitorPage: React.FC = () => {
marginBottom: 24, marginBottom: 24,
}}> }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<span style={{ font: 'var(--text-h3)' }}>/</span> <span style={{ font: 'var(--text-h3)' }}>{t('trading_volume_trend')}</span>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
{['1H', '24H', '7D', '30D'].map(p => ( {['1H', '24H', '7D', '30D'].map(p => (
<button key={p} style={{ <button key={p} style={{
@ -76,9 +77,9 @@ export const TradingMonitorPage: React.FC = () => {
overflow: 'hidden', overflow: 'hidden',
}}> }}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)', display: 'flex', justifyContent: 'space-between' }}> <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--color-border-light)', display: 'flex', justifyContent: 'space-between' }}>
<span style={{ font: 'var(--text-h3)' }}></span> <span style={{ font: 'var(--text-h3)' }}>{t('trading_order_management')}</span>
<input <input
placeholder="搜索订单号..." placeholder={t('trading_search_order')}
style={{ style={{
width: 240, width: 240,
height: 32, height: 32,
@ -92,7 +93,7 @@ export const TradingMonitorPage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)' }}> <tr style={{ background: 'var(--color-gray-50)' }}>
{['订单号', '类型', '券名称', '买方', '卖方', '金额', '状态', '时间'].map(h => ( {[t('trading_th_order_id'), t('trading_th_type'), t('trading_th_coupon_name'), t('trading_th_buyer'), t('trading_th_seller'), t('trading_th_amount'), t('trading_th_status'), t('trading_th_time')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -109,7 +110,7 @@ export const TradingMonitorPage: React.FC = () => {
GNX-20260210-{String(1200 - i).padStart(6, '0')} GNX-20260210-{String(1200 - i).padStart(6, '0')}
</td> </td>
<td style={{ font: 'var(--text-label-sm)', padding: '10px 14px' }}> <td style={{ font: 'var(--text-label-sm)', padding: '10px 14px' }}>
{['购买', '转售', '核销', '转赠'][i % 4]} {[t('trading_type_purchase'), t('trading_type_resell'), t('trading_type_redeem'), t('trading_type_transfer')][i % 4]}
</td> </td>
<td style={{ font: 'var(--text-body-sm)', padding: '10px 14px' }}> <td style={{ font: 'var(--text-body-sm)', padding: '10px 14px' }}>
{['星巴克 $25', 'Amazon $100', 'Nike $80', 'Target $30'][i % 4]} {['星巴克 $25', 'Amazon $100', 'Nike $80', 'Target $30'][i % 4]}
@ -133,7 +134,7 @@ export const TradingMonitorPage: React.FC = () => {
color: i < 6 ? 'var(--color-success)' : 'var(--color-warning)', color: i < 6 ? 'var(--color-success)' : 'var(--color-warning)',
font: 'var(--text-caption)', font: 'var(--text-caption)',
}}> }}>
{i < 6 ? '完成' : '争议'} {i < 6 ? t('trading_status_completed') : t('trading_status_dispute')}
</span> </span>
</td> </td>
<td style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px' }}> <td style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', padding: '10px 14px' }}>

View File

@ -1,6 +1,7 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { t } from '@/i18n/locales';
/** /**
* D3. * D3.
@ -21,7 +22,7 @@ interface User {
const mockUsers: User[] = [ const mockUsers: User[] = [
{ id: 'U-001', phone: '138****1234', email: 'john@mail.com', kycLevel: 2, couponCount: 15, totalTraded: '$2,340', riskTags: [], createdAt: '2026-01-10' }, { id: 'U-001', phone: '138****1234', email: 'john@mail.com', kycLevel: 2, couponCount: 15, totalTraded: '$2,340', riskTags: [], createdAt: '2026-01-10' },
{ id: 'U-002', phone: '139****5678', email: 'jane@mail.com', kycLevel: 1, couponCount: 8, totalTraded: '$890', riskTags: ['高频交易'], createdAt: '2026-01-15' }, { id: 'U-002', phone: '139****5678', email: 'jane@mail.com', kycLevel: 1, couponCount: 8, totalTraded: '$890', riskTags: [t('risk_type_high_freq')], createdAt: '2026-01-15' },
{ id: 'U-003', phone: '137****9012', email: 'bob@mail.com', kycLevel: 3, couponCount: 42, totalTraded: '$12,450', riskTags: [], createdAt: '2025-12-01' }, { id: 'U-003', phone: '137****9012', email: 'bob@mail.com', kycLevel: 3, couponCount: 42, totalTraded: '$12,450', riskTags: [], createdAt: '2025-12-01' },
{ id: 'U-004', phone: '136****3456', email: 'alice@mail.com', kycLevel: 0, couponCount: 0, totalTraded: '-', riskTags: [], createdAt: '2026-02-09' }, { id: 'U-004', phone: '136****3456', email: 'alice@mail.com', kycLevel: 0, couponCount: 0, totalTraded: '-', riskTags: [], createdAt: '2026-02-09' },
]; ];
@ -54,12 +55,12 @@ export const UserManagementPage: React.FC = () => {
return ( return (
<div> <div>
<h1 style={{ font: 'var(--text-h1)', marginBottom: 24 }}></h1> <h1 style={{ font: 'var(--text-h1)', marginBottom: 24 }}>{t('user_management_title')}</h1>
{/* Search + Filters */} {/* Search + Filters */}
<div style={{ display: 'flex', gap: 12, marginBottom: 20 }}> <div style={{ display: 'flex', gap: 12, marginBottom: 20 }}>
<input <input
placeholder="搜索手机号/邮箱/用户ID..." placeholder={t('user_search_placeholder')}
value={search} value={search}
onChange={e => setSearch(e.target.value)} onChange={e => setSearch(e.target.value)}
style={{ style={{
@ -86,7 +87,7 @@ export const UserManagementPage: React.FC = () => {
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
}} }}
> >
{level === null ? '全部' : `L${level}`} {level === null ? t('all') : `L${level}`}
</button> </button>
))} ))}
</div> </div>
@ -101,7 +102,7 @@ export const UserManagementPage: React.FC = () => {
<table style={{ width: '100%', borderCollapse: 'collapse' }}> <table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead> <thead>
<tr style={{ background: 'var(--color-gray-50)', borderBottom: '1px solid var(--color-border)' }}> <tr style={{ background: 'var(--color-gray-50)', borderBottom: '1px solid var(--color-border)' }}>
{['用户ID', '手机号', '邮箱', 'KYC等级', '持券数', '交易额', '风险标签', '注册时间', '操作'].map(h => ( {[t('user_id'), t('user_phone'), t('user_email'), t('user_kyc_level'), t('user_coupon_count'), t('user_total_traded'), t('user_risk_tags'), t('user_created_at'), t('actions')].map(h => (
<th key={h} style={{ <th key={h} style={{
font: 'var(--text-label-sm)', font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)', color: 'var(--color-text-tertiary)',
@ -145,7 +146,7 @@ export const UserManagementPage: React.FC = () => {
cursor: 'pointer', cursor: 'pointer',
font: 'var(--text-caption)', font: 'var(--text-caption)',
color: 'var(--color-primary)', color: 'var(--color-primary)',
}}></button> }}>{t('details')}</button>
</td> </td>
</tr> </tr>
))} ))}

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component // Taro mini-program component
/** /**
@ -39,7 +40,7 @@ const AiGuide: React.FC<AiGuideProps> = ({ type }) => {
</view> </view>
<view className="ai-bubble-content"> <view className="ai-bubble-content">
<text className="ai-bubble-text"> <text className="ai-bubble-text">
AI助手"星巴克" {t('ai_guide_greeting')}
</text> </text>
</view> </view>
<view className="ai-bubble-close"> <view className="ai-bubble-close">

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component // Taro mini-program component
/** /**
@ -44,11 +45,11 @@ const ShareCard: React.FC<ShareCardProps> = ({
{/* Footer with QR */} {/* Footer with QR */}
<view className="share-footer"> <view className="share-footer">
<view className="share-qr"> <view className="share-qr">
<text className="share-qr-placeholder"></text> <text className="share-qr-placeholder">{t('share_miniapp_code')}</text>
</view> </view>
<view className="share-cta"> <view className="share-cta">
<text className="share-cta-title"></text> <text className="share-cta-title">{t('share_scan_miniapp')}</text>
<text className="share-cta-desc"></text> <text className="share-cta-desc">{t('share_buy_coupons')}</text>
</view> </view>
</view> </view>
</view> </view>

View File

@ -188,6 +188,130 @@ const translations: Record<Locale, Record<string, string>> = {
'error_unknown': '未知错误', 'error_unknown': '未知错误',
'error_not_found': '页面不存在', 'error_not_found': '页面不存在',
'error_unauthorized': '请先登录', 'error_unauthorized': '请先登录',
// ── Home (additional) ──
'home_ai_text': '根据你的偏好,发现了高性价比券',
'home_banner_new_user': '新用户专享',
'home_banner_new_user_desc': '首单立减¥10',
'home_banner_flash': '限时折扣',
'home_banner_flash_desc': '全场低至7折',
'home_banner_hot': '热门推荐',
'home_banner_hot_desc': '精选高折扣券',
'home_category_food': '餐饮',
'home_category_shopping': '购物',
'home_category_entertainment': '娱乐',
'home_category_travel': '出行',
// ── Login (additional) ──
'app_slogan': '发现优质好券',
'login_wechat': '微信一键登录',
'login_or': '或',
'login_phone': '手机号',
'login_code': '验证码',
'login_btn': '登录',
'login_agree': '登录即表示同意',
// ── Detail (additional) ──
'coupon_type_label': '类型',
'coupon_store': '使用门店',
'coupon_type_consumer': '消费券',
'coupon_price_save': '比面值节省',
'coupon_utility_notice': '您正在购买消费券用于消费',
'coupon_rule_universal': '全国星巴克门店通用',
'coupon_rule_giftable': '可转赠给好友',
'coupon_rule_anytime': '有效期内随时使用',
'coupon_rule_no_stack': '不可叠加使用',
'order_total': '合计',
'coupon_buy_now': '立即购买',
// ── My Coupons (additional) ──
'coupon_valid_until_prefix': '有效期至',
// ── Redeem (additional) ──
'redeem_qr_label': '二维码',
'redeem_valid_time': '有效时间',
'redeem_refresh': '刷新券码',
'redeem_hint': '请将此码出示给商户扫描,屏幕已自动调至最高亮度',
// ── Profile (additional) ──
'profile_kyc_basic': '基础认证',
'profile_owned': '持有券',
'profile_payment': '支付管理',
'profile_notifications': '消息通知',
'profile_language': '语言 / Language',
'profile_currency': '货币',
'download_app_full_desc': '解锁二级市场交易、P2P转赠等完整功能',
'download_btn': '下载',
// ── H5 Share ──
'share_from_genex': '来自 Genex 的分享',
'share_open_app': '打开 App 购买',
'share_miniapp_buy': '小程序购买',
'share_to_friends': '分享给好友',
// ── H5 Activity ──
'activity_flash': '限时活动',
'activity_title': '限时特惠 | 新用户专享',
'activity_subtitle': '精选大牌优惠券折扣低至7折起',
'activity_countdown': '距活动结束',
'activity_featured': '精选好券',
'activity_limited': '限量抢购,先到先得',
'activity_tag_hot': '爆款',
'activity_tag_selling': '热卖',
'activity_tag_new': '新品',
'activity_buy_now': '立即抢购',
'activity_rules': '活动规则',
'activity_rule_1': '活动时间2026年2月10日 - 2026年2月28日',
'activity_rule_2': '每位用户限购每种券3张活动优惠券不与其他优惠叠加使用',
'activity_rule_3': '优惠券自购买之日起30天内有效过期自动作废',
'activity_rule_4': '活动券仅限新注册用户首次购买使用',
'activity_rule_5': '如遇商品售罄Genex保留调整活动内容的权利',
'activity_rule_6': '退款将原路返回处理时间为1-3个工作日',
'activity_rule_7': '如有疑问请联系客服support@genex.com',
'activity_participants': '已有 {count} 人参与',
'app_platform_slogan': '全球券资产交易平台',
// ── H5 Register ──
'activity_new_user': '新用户专享',
'activity_first_order': '首单立减 $10限时优惠',
'activity_coupons': '活动好券',
'activity_brand': '品牌',
'activity_join_now': '立即参与',
'register_join': '加入 Genex',
'register_slogan': '让每一张券都有价值',
'register_benefit': '注册即享首单立减优惠',
'register_benefit_coupons': '海量优惠券',
'register_benefit_coupons_desc': '餐饮、购物、娱乐全覆盖',
'register_benefit_discount': '超值折扣',
'register_benefit_discount_desc': '最低7折起省钱又省心',
'register_benefit_safe': '安全交易',
'register_benefit_safe_desc': '平台担保,放心购买',
'register_now': '立即注册',
'register_login': '已有账号,登录',
'register_why_genex': '为什么选择 Genex',
'register_benefit_coupons_full': '覆盖餐饮、购物、娱乐等20+品类,全球大牌低价好券',
'register_benefit_safe_full': '平台担保交易,资金托管机制,保障每一笔交易安全可靠',
'register_benefit_ai': 'AI智能推荐',
'register_benefit_ai_desc': '基于您的偏好智能推荐高性价比好券,省时又省钱',
'register_create_account': '创建您的账户',
'register_other_methods': '其他登录方式',
'register_has_account': '已有账号?',
'register_login_now': '立即登录',
'trust_secure': '安全认证',
'trust_guarantee': '用户保障',
'trust_privacy': '隐私保护',
'trust_encryption': '您的信息受到银行级加密保护',
// ── AI Guide ──
'ai_guide_greeting': '你好我是AI助手可以帮你找到最适合的券。试试搜索"星巴克"',
// ── Share Card ──
'share_miniapp_code': '小程序码',
'share_scan_miniapp': '长按识别小程序码',
'share_buy_coupons': '立即抢购优惠好券',
// ── Coupon Detail (stores) ──
'coupon_stores_count': '全国 12,800+ 门店',
}, },
'en-US': { 'en-US': {
@ -358,6 +482,130 @@ const translations: Record<Locale, Record<string, string>> = {
'error_unknown': 'Unknown Error', 'error_unknown': 'Unknown Error',
'error_not_found': 'Page Not Found', 'error_not_found': 'Page Not Found',
'error_unauthorized': 'Please Log In', 'error_unauthorized': 'Please Log In',
// ── Home (additional) ──
'home_ai_text': 'We found great deals based on your preferences',
'home_banner_new_user': 'New User Exclusive',
'home_banner_new_user_desc': 'Save ¥10 on first order',
'home_banner_flash': 'Flash Sale',
'home_banner_flash_desc': 'Up to 30% off',
'home_banner_hot': 'Hot Picks',
'home_banner_hot_desc': 'Best discount coupons',
'home_category_food': 'Food',
'home_category_shopping': 'Shopping',
'home_category_entertainment': 'Fun',
'home_category_travel': 'Travel',
// ── Login (additional) ──
'app_slogan': 'Discover great coupons',
'login_wechat': 'Login with WeChat',
'login_or': 'or',
'login_phone': 'Phone number',
'login_code': 'Verification code',
'login_btn': 'Log In',
'login_agree': 'By logging in, you agree to the',
// ── Detail (additional) ──
'coupon_type_label': 'Type',
'coupon_store': 'Stores',
'coupon_type_consumer': 'Consumer Coupon',
'coupon_price_save': 'Save vs. face value',
'coupon_utility_notice': 'You are buying a consumer coupon for consumption',
'coupon_rule_universal': 'Valid at all Starbucks stores nationwide',
'coupon_rule_giftable': 'Can be gifted to friends',
'coupon_rule_anytime': 'Use anytime before expiry',
'coupon_rule_no_stack': 'Cannot be stacked with other offers',
'order_total': 'Total',
'coupon_buy_now': 'Buy Now',
// ── My Coupons (additional) ──
'coupon_valid_until_prefix': 'Valid until',
// ── Redeem (additional) ──
'redeem_qr_label': 'QR Code',
'redeem_valid_time': 'Valid for',
'redeem_refresh': 'Refresh Code',
'redeem_hint': 'Show this code to the merchant for scanning. Screen brightness has been maximized.',
// ── Profile (additional) ──
'profile_kyc_basic': 'Basic Verified',
'profile_owned': 'Owned',
'profile_payment': 'Payment',
'profile_notifications': 'Notifications',
'profile_language': 'Language',
'profile_currency': 'Currency',
'download_app_full_desc': 'Unlock secondary market trading, P2P gifting & more',
'download_btn': 'Download',
// ── H5 Share ──
'share_from_genex': 'Shared via Genex',
'share_open_app': 'Open App to Buy',
'share_miniapp_buy': 'Buy in Mini Program',
'share_to_friends': 'Share with Friends',
// ── H5 Activity ──
'activity_flash': 'Flash Sale',
'activity_title': 'Flash Deals | New User Exclusive',
'activity_subtitle': 'Top brand coupons from 30% off',
'activity_countdown': 'Ends in',
'activity_featured': 'Featured Coupons',
'activity_limited': 'Limited stock, first come first served',
'activity_tag_hot': 'Hot',
'activity_tag_selling': 'Best Seller',
'activity_tag_new': 'New',
'activity_buy_now': 'Buy Now',
'activity_rules': 'Rules',
'activity_rule_1': 'Event period: Feb 10, 2026 - Feb 28, 2026',
'activity_rule_2': 'Each user can buy up to 3 of each coupon. Cannot be combined with other offers.',
'activity_rule_3': 'Coupons are valid for 30 days from purchase date.',
'activity_rule_4': 'Activity coupons are for new users\' first purchase only.',
'activity_rule_5': 'Genex reserves the right to adjust activity contents if items are sold out.',
'activity_rule_6': 'Refunds will be returned via original payment method within 1-3 business days.',
'activity_rule_7': 'For questions, contact support: support@genex.com',
'activity_participants': '{count} people joined',
'app_platform_slogan': 'Global Coupon Asset Trading Platform',
// ── H5 Register ──
'activity_new_user': 'New User Exclusive',
'activity_first_order': 'Save $10 on first order, limited time',
'activity_coupons': 'Event Coupons',
'activity_brand': 'Brand',
'activity_join_now': 'Join Now',
'register_join': 'Join Genex',
'register_slogan': 'Make every coupon count',
'register_benefit': 'Get first-order discount on signup',
'register_benefit_coupons': 'Massive Coupons',
'register_benefit_coupons_desc': 'Dining, shopping, entertainment & more',
'register_benefit_discount': 'Great Discounts',
'register_benefit_discount_desc': 'Up to 30% off, save smart',
'register_benefit_safe': 'Secure Trading',
'register_benefit_safe_desc': 'Platform guaranteed, buy with confidence',
'register_now': 'Sign Up Now',
'register_login': 'Already have an account? Log In',
'register_why_genex': 'Why Genex?',
'register_benefit_coupons_full': 'Covering 20+ categories including dining, shopping, entertainment. Top brand coupons at low prices.',
'register_benefit_safe_full': 'Platform-guaranteed transactions with escrow mechanism for secure trading.',
'register_benefit_ai': 'AI Recommendations',
'register_benefit_ai_desc': 'Smart recommendations based on your preferences. Save time and money.',
'register_create_account': 'Create Your Account',
'register_other_methods': 'Other login methods',
'register_has_account': 'Already have an account?',
'register_login_now': 'Log In',
'trust_secure': 'Security Certified',
'trust_guarantee': 'User Protection',
'trust_privacy': 'Privacy Protected',
'trust_encryption': 'Your data is protected with bank-grade encryption',
// ── AI Guide ──
'ai_guide_greeting': 'Hi! I\'m the AI assistant. I can help you find the best coupons. Try searching "Starbucks"!',
// ── Share Card ──
'share_miniapp_code': 'Mini Program Code',
'share_scan_miniapp': 'Long press to scan',
'share_buy_coupons': 'Buy great coupons now',
// ── Coupon Detail (stores) ──
'coupon_stores_count': '12,800+ stores nationwide',
}, },
'ja-JP': { 'ja-JP': {
@ -528,5 +776,129 @@ const translations: Record<Locale, Record<string, string>> = {
'error_unknown': '不明なエラー', 'error_unknown': '不明なエラー',
'error_not_found': 'ページが見つかりません', 'error_not_found': 'ページが見つかりません',
'error_unauthorized': 'ログインしてください', 'error_unauthorized': 'ログインしてください',
// ── Home (additional) ──
'home_ai_text': 'あなたの好みに合ったお得なクーポンを見つけました',
'home_banner_new_user': '新規ユーザー限定',
'home_banner_new_user_desc': '初回注文¥10引き',
'home_banner_flash': 'タイムセール',
'home_banner_flash_desc': '最大30%OFF',
'home_banner_hot': '人気のおすすめ',
'home_banner_hot_desc': '厳選高割引クーポン',
'home_category_food': 'グルメ',
'home_category_shopping': 'ショッピング',
'home_category_entertainment': 'エンタメ',
'home_category_travel': '旅行',
// ── Login (additional) ──
'app_slogan': 'お得なクーポンを発見',
'login_wechat': 'WeChatでログイン',
'login_or': 'または',
'login_phone': '電話番号',
'login_code': '認証コード',
'login_btn': 'ログイン',
'login_agree': 'ログインすると以下に同意したものとみなされます',
// ── Detail (additional) ──
'coupon_type_label': '種類',
'coupon_store': '利用店舗',
'coupon_type_consumer': '消費クーポン',
'coupon_price_save': '額面よりお得',
'coupon_utility_notice': '消費用のクーポンを購入します',
'coupon_rule_universal': '全国のスターバックス店舗で利用可能',
'coupon_rule_giftable': '友達にプレゼント可能',
'coupon_rule_anytime': '有効期限内いつでも利用可能',
'coupon_rule_no_stack': '他のクーポンとの併用不可',
'order_total': '合計',
'coupon_buy_now': '今すぐ購入',
// ── My Coupons (additional) ──
'coupon_valid_until_prefix': '有効期限',
// ── Redeem (additional) ──
'redeem_qr_label': 'QRコード',
'redeem_valid_time': '有効時間',
'redeem_refresh': 'コードを更新',
'redeem_hint': 'このコードを店舗スタッフに提示してください。画面の明るさが最大に設定されました。',
// ── Profile (additional) ──
'profile_kyc_basic': '基本認証済み',
'profile_owned': '保有中',
'profile_payment': '決済管理',
'profile_notifications': 'お知らせ',
'profile_language': '言語',
'profile_currency': '通貨',
'download_app_full_desc': '二次市場取引、P2Pギフトなどの機能をアンロック',
'download_btn': 'ダウンロード',
// ── H5 Share ──
'share_from_genex': 'Genex からのシェア',
'share_open_app': 'アプリで購入',
'share_miniapp_buy': 'ミニプログラムで購入',
'share_to_friends': '友達にシェア',
// ── H5 Activity ──
'activity_flash': 'タイムセール',
'activity_title': '期間限定 | 新規ユーザー限定',
'activity_subtitle': '人気ブランドクーポンが最大30%OFF',
'activity_countdown': '終了まで',
'activity_featured': '厳選クーポン',
'activity_limited': '数量限定、お早めに',
'activity_tag_hot': '人気',
'activity_tag_selling': '売れ筋',
'activity_tag_new': '新着',
'activity_buy_now': '今すぐ購入',
'activity_rules': 'キャンペーンルール',
'activity_rule_1': 'キャンペーン期間2026年2月10日2026年2月28日',
'activity_rule_2': 'お一人様各クーポン3枚まで。他の割引との併用不可。',
'activity_rule_3': 'クーポンは購入日から30日間有効です。',
'activity_rule_4': 'キャンペーンクーポンは新規ユーザーの初回購入のみ利用可能です。',
'activity_rule_5': '商品完売の場合、キャンペーン内容を変更する場合があります。',
'activity_rule_6': '返金は元の決済方法に1-3営業日以内に返金されます。',
'activity_rule_7': 'ご質問はサポートへsupport@genex.com',
'activity_participants': '{count}人が参加',
'app_platform_slogan': 'グローバルクーポン資産取引プラットフォーム',
// ── H5 Register ──
'activity_new_user': '新規ユーザー限定',
'activity_first_order': '初回注文$10割引、期間限定',
'activity_coupons': 'キャンペーンクーポン',
'activity_brand': 'ブランド',
'activity_join_now': '今すぐ参加',
'register_join': 'Genex に参加',
'register_slogan': 'すべてのクーポンに価値を',
'register_benefit': '登録で初回割引をゲット',
'register_benefit_coupons': '豊富なクーポン',
'register_benefit_coupons_desc': 'グルメ、ショッピング、エンタメを網羅',
'register_benefit_discount': 'お得な割引',
'register_benefit_discount_desc': '最大30%OFF、賢く節約',
'register_benefit_safe': '安全な取引',
'register_benefit_safe_desc': 'プラットフォーム保証、安心して購入',
'register_now': '今すぐ登録',
'register_login': 'アカウントをお持ちの方はログイン',
'register_why_genex': 'なぜGenex',
'register_benefit_coupons_full': 'グルメ、ショッピング、エンタメなど20+カテゴリーをカバー。人気ブランドのお得なクーポン。',
'register_benefit_safe_full': 'プラットフォーム保証取引、エスクロー機構で安全な取引を保障。',
'register_benefit_ai': 'AIスマートレコメンド',
'register_benefit_ai_desc': 'お好みに合わせたスマートなクーポン提案。時間もお金も節約。',
'register_create_account': 'アカウントを作成',
'register_other_methods': 'その他のログイン方法',
'register_has_account': 'アカウントをお持ちですか?',
'register_login_now': 'ログイン',
'trust_secure': 'セキュリティ認証',
'trust_guarantee': 'ユーザー保護',
'trust_privacy': 'プライバシー保護',
'trust_encryption': 'お客様の情報は銀行レベルの暗号化で保護されています',
// ── AI Guide ──
'ai_guide_greeting': 'こんにちはAIアシスタントです。最適なクーポンを見つけるお手伝いをします。「スターバックス」で検索してみてください',
// ── Share Card ──
'share_miniapp_code': 'ミニプログラムコード',
'share_scan_miniapp': '長押しして読み取る',
'share_buy_coupons': 'お得なクーポンを今すぐ購入',
// ── Coupon Detail (stores) ──
'coupon_stores_count': '全国12,800+店舗',
}, },
}; };

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program - Coupon Detail + Purchase // Taro mini-program - Coupon Detail + Purchase
/** /**
@ -38,16 +39,16 @@ const DetailPage: React.FC = () => {
<text className="price-original">¥25</text> <text className="price-original">¥25</text>
<view className="discount-tag">8.5</view> <view className="discount-tag">8.5</view>
</view> </view>
<text className="price-save"> ¥3.75</text> <text className="price-save">{t('coupon_price_save')} ¥3.75</text>
</view> </view>
{/* Info List */} {/* Info List */}
<view className="info-card"> <view className="info-card">
{[ {[
{ label: '面值', value: '¥25.00' }, { label: t('coupon_face_value'), value: '¥25.00' },
{ label: '有效期', value: '2026/12/31' }, { label: t('coupon_valid_until'), value: '2026/12/31' },
{ label: '类型', value: '消费券' }, { label: t('coupon_type_label'), value: t('coupon_type_consumer') },
{ label: '使用门店', value: '全国 12,800+ 门店' }, { label: t('coupon_store'), value: t('coupon_stores_count') },
].map((item, i) => ( ].map((item, i) => (
<view key={i} className="info-row"> <view key={i} className="info-row">
<text className="info-label">{item.label}</text> <text className="info-label">{item.label}</text>
@ -58,12 +59,12 @@ const DetailPage: React.FC = () => {
{/* Rules */} {/* Rules */}
<view className="rules-card"> <view className="rules-card">
<text className="rules-title">使</text> <text className="rules-title">{t('coupon_description')}</text>
{[ {[
'全国星巴克门店通用', t('coupon_rule_universal'),
'可转赠给好友', t('coupon_rule_giftable'),
'有效期内随时使用', t('coupon_rule_anytime'),
'不可叠加使用', t('coupon_rule_no_stack'),
].map((rule, i) => ( ].map((rule, i) => (
<view key={i} className="rule-item"> <view key={i} className="rule-item">
<text className="rule-dot"></text> <text className="rule-dot"></text>
@ -75,18 +76,18 @@ const DetailPage: React.FC = () => {
{/* Utility Track Notice */} {/* Utility Track Notice */}
<view className="utility-notice"> <view className="utility-notice">
<text className="utility-icon"></text> <text className="utility-icon"></text>
<text className="utility-text"></text> <text className="utility-text">{t('coupon_utility_notice')}</text>
</view> </view>
</view> </view>
{/* Bottom Buy Bar */} {/* Bottom Buy Bar */}
<view className="buy-bar"> <view className="buy-bar">
<view className="buy-bar-price"> <view className="buy-bar-price">
<text className="buy-label"></text> <text className="buy-label">{t('order_total')}</text>
<text className="buy-price">¥21.25</text> <text className="buy-price">¥21.25</text>
</view> </view>
<view className="buy-button"> <view className="buy-button">
<text className="buy-button-text"></text> <text className="buy-button-text">{t('coupon_buy_now')}</text>
</view> </view>
</view> </view>
</view> </view>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component // Taro mini-program component
/** /**
@ -15,14 +16,14 @@ const H5ActivityPage: React.FC = () => {
<view className="hero-banner"> <view className="hero-banner">
<view className="hero-badge"> <view className="hero-badge">
<text className="hero-badge-icon">🔥</text> <text className="hero-badge-icon">🔥</text>
<text className="hero-badge-text"></text> <text className="hero-badge-text">{t('activity_flash')}</text>
</view> </view>
<text className="hero-title"> | </text> <text className="hero-title">{t('activity_title')}</text>
<text className="hero-subtitle">7</text> <text className="hero-subtitle">{t('activity_subtitle')}</text>
{/* Countdown Timer */} {/* Countdown Timer */}
<view className="countdown-section"> <view className="countdown-section">
<text className="countdown-label"></text> <text className="countdown-label">{t('activity_countdown')}</text>
<view className="countdown-timer"> <view className="countdown-timer">
<view className="countdown-block"> <view className="countdown-block">
<text className="countdown-num">02</text> <text className="countdown-num">02</text>
@ -46,8 +47,8 @@ const H5ActivityPage: React.FC = () => {
{/* Featured Coupon Cards */} {/* Featured Coupon Cards */}
<view className="coupon-section"> <view className="coupon-section">
<view className="section-header"> <view className="section-header">
<text className="section-title"></text> <text className="section-title">{t('activity_featured')}</text>
<text className="section-subtitle"></text> <text className="section-subtitle">{t('activity_limited')}</text>
</view> </view>
{[ {[
@ -58,7 +59,7 @@ const H5ActivityPage: React.FC = () => {
originalPrice: '¥50.00', originalPrice: '¥50.00',
discountPrice: '¥35.00', discountPrice: '¥35.00',
discount: '7折', discount: '7折',
tag: '爆款', tag: t('activity_tag_hot'),
}, },
{ {
brand: 'Amazon', brand: 'Amazon',
@ -67,7 +68,7 @@ const H5ActivityPage: React.FC = () => {
originalPrice: '¥200.00', originalPrice: '¥200.00',
discountPrice: '¥156.00', discountPrice: '¥156.00',
discount: '7.8折', discount: '7.8折',
tag: '热卖', tag: t('activity_tag_selling'),
}, },
{ {
brand: 'Nike', brand: 'Nike',
@ -76,7 +77,7 @@ const H5ActivityPage: React.FC = () => {
originalPrice: '¥100.00', originalPrice: '¥100.00',
discountPrice: '¥72.00', discountPrice: '¥72.00',
discount: '7.2折', discount: '7.2折',
tag: '新品', tag: t('activity_tag_new'),
}, },
].map((coupon, i) => ( ].map((coupon, i) => (
<view key={i} className="coupon-card"> <view key={i} className="coupon-card">
@ -113,7 +114,7 @@ const H5ActivityPage: React.FC = () => {
{/* Buy Button */} {/* Buy Button */}
<view className="coupon-buy-btn"> <view className="coupon-buy-btn">
<text className="coupon-buy-text"></text> <text className="coupon-buy-text">{t('activity_buy_now')}</text>
</view> </view>
</view> </view>
))} ))}
@ -125,18 +126,18 @@ const H5ActivityPage: React.FC = () => {
<view className="rules-icon"> <view className="rules-icon">
<text className="rules-icon-text">📋</text> <text className="rules-icon-text">📋</text>
</view> </view>
<text className="rules-title"></text> <text className="rules-title">{t('activity_rules')}</text>
</view> </view>
<view className="rules-list"> <view className="rules-list">
{[ {[
'活动时间2026年2月10日 - 2026年2月28日', t('activity_rule_1'),
'每位用户限购每种券3张活动优惠券不与其他优惠叠加使用', t('activity_rule_2'),
'优惠券自购买之日起30天内有效过期自动作废', t('activity_rule_3'),
'活动券仅限新注册用户首次购买使用', t('activity_rule_4'),
'如遇商品售罄Genex保留调整活动内容的权利', t('activity_rule_5'),
'退款将原路返回处理时间为1-3个工作日', t('activity_rule_6'),
'如有疑问请联系客服support@genex.com', t('activity_rule_7'),
].map((rule, i) => ( ].map((rule, i) => (
<view key={i} className="rule-item"> <view key={i} className="rule-item">
<view className="rule-dot" /> <view className="rule-dot" />
@ -154,7 +155,7 @@ const H5ActivityPage: React.FC = () => {
</view> </view>
<view className="share-btn"> <view className="share-btn">
<text className="share-btn-icon">📤</text> <text className="share-btn-icon">📤</text>
<text className="share-btn-text"></text> <text className="share-btn-text">{t('share_to_friends')}</text>
</view> </view>
</view> </view>
@ -166,7 +167,7 @@ const H5ActivityPage: React.FC = () => {
</view> </view>
<text className="footer-logo-name">Genex</text> <text className="footer-logo-name">Genex</text>
</view> </view>
<text className="footer-slogan"></text> <text className="footer-slogan">{t('app_platform_slogan')}</text>
<text className="footer-copyright">© 2026 Genex. All rights reserved.</text> <text className="footer-copyright">© 2026 Genex. All rights reserved.</text>
</view> </view>
</view> </view>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component // Taro mini-program component
/** /**
@ -19,28 +20,28 @@ const H5RegisterPage: React.FC = () => {
<text className="brand-logo-letter">G</text> <text className="brand-logo-letter">G</text>
</view> </view>
<text className="brand-app-name">Genex</text> <text className="brand-app-name">Genex</text>
<text className="brand-tagline"></text> <text className="brand-tagline">{t('app_platform_slogan')}</text>
</view> </view>
{/* Benefits Section */} {/* Benefits Section */}
<view className="benefits-section"> <view className="benefits-section">
<text className="benefits-title"> Genex</text> <text className="benefits-title">{t('register_why_genex')}</text>
<view className="benefits-grid"> <view className="benefits-grid">
{[ {[
{ {
icon: '🎫', icon: '🎫',
title: '海量优惠券', title: t('register_benefit_coupons'),
desc: '覆盖餐饮、购物、娱乐等20+品类,全球大牌低价好券', desc: t('register_benefit_coupons_full'),
}, },
{ {
icon: '🔒', icon: '🔒',
title: '安全交易', title: t('register_benefit_safe'),
desc: '平台担保交易,资金托管机制,保障每一笔交易安全可靠', desc: t('register_benefit_safe_full'),
}, },
{ {
icon: '🤖', icon: '🤖',
title: 'AI智能推荐', title: t('register_benefit_ai'),
desc: '基于您的偏好智能推荐高性价比好券,省时又省钱', desc: t('register_benefit_ai_desc'),
}, },
].map((benefit, i) => ( ].map((benefit, i) => (
<view key={i} className="benefit-card"> <view key={i} className="benefit-card">
@ -56,7 +57,7 @@ const H5RegisterPage: React.FC = () => {
{/* Registration Form */} {/* Registration Form */}
<view className="form-section"> <view className="form-section">
<text className="form-title"></text> <text className="form-title">{t('register_create_account')}</text>
{/* Phone Input */} {/* Phone Input */}
<view className="form-input-group"> <view className="form-input-group">
@ -68,7 +69,7 @@ const H5RegisterPage: React.FC = () => {
</view> </view>
<input <input
className="form-input" className="form-input"
placeholder="请输入手机号" placeholder={t('login_phone_placeholder')}
type="number" type="number"
maxlength={11} maxlength={11}
/> />
@ -82,13 +83,13 @@ const H5RegisterPage: React.FC = () => {
<text className="form-input-icon">🔑</text> <text className="form-input-icon">🔑</text>
<input <input
className="form-input" className="form-input"
placeholder="请输入验证码" placeholder={t('login_code_placeholder')}
type="number" type="number"
maxlength={6} maxlength={6}
/> />
</view> </view>
<view className="form-code-btn"> <view className="form-code-btn">
<text className="form-code-btn-text"></text> <text className="form-code-btn-text">{t('login_send_code')}</text>
</view> </view>
</view> </view>
</view> </view>
@ -99,35 +100,35 @@ const H5RegisterPage: React.FC = () => {
<view className="terms-checkbox-inner" /> <view className="terms-checkbox-inner" />
</view> </view>
<view className="terms-text-wrap"> <view className="terms-text-wrap">
<text className="terms-text-normal"></text> <text className="terms-text-normal">{t('login_agree_prefix')}</text>
<text className="terms-text-link"></text> <text className="terms-text-link">{`${t('login_user_agreement')}`}</text>
<text className="terms-text-normal"></text> <text className="terms-text-normal">{t('login_and')}</text>
<text className="terms-text-link"></text> <text className="terms-text-link">{`${t('login_privacy_policy')}`}</text>
</view> </view>
</view> </view>
{/* Primary CTA Button */} {/* Primary CTA Button */}
<view className="register-btn"> <view className="register-btn">
<text className="register-btn-text"></text> <text className="register-btn-text">{t('register_now')}</text>
</view> </view>
{/* Social Login Divider */} {/* Social Login Divider */}
<view className="social-divider"> <view className="social-divider">
<view className="social-divider-line" /> <view className="social-divider-line" />
<text className="social-divider-text"></text> <text className="social-divider-text">{t('register_other_methods')}</text>
<view className="social-divider-line" /> <view className="social-divider-line" />
</view> </view>
{/* WeChat Login */} {/* WeChat Login */}
<view className="wechat-login-btn"> <view className="wechat-login-btn">
<text className="wechat-login-icon">💬</text> <text className="wechat-login-icon">💬</text>
<text className="wechat-login-text"></text> <text className="wechat-login-text">{t('login_wechat')}</text>
</view> </view>
{/* Already Have Account */} {/* Already Have Account */}
<view className="login-link-row"> <view className="login-link-row">
<text className="login-link-text"></text> <text className="login-link-text">{t('register_has_account')}</text>
<text className="login-link-action"></text> <text className="login-link-action">{t('register_login_now')}</text>
</view> </view>
</view> </view>
@ -135,9 +136,9 @@ const H5RegisterPage: React.FC = () => {
<view className="trust-section"> <view className="trust-section">
<view className="trust-badges"> <view className="trust-badges">
{[ {[
{ icon: '🛡️', label: '安全认证' }, { icon: '🛡️', label: t('trust_secure') },
{ icon: '✅', label: '用户保障' }, { icon: '✅', label: t('trust_guarantee') },
{ icon: '🔐', label: '隐私保护' }, { icon: '🔐', label: t('trust_privacy') },
].map((badge, i) => ( ].map((badge, i) => (
<view key={i} className="trust-badge-item"> <view key={i} className="trust-badge-item">
<text className="trust-badge-icon">{badge.icon}</text> <text className="trust-badge-icon">{badge.icon}</text>
@ -145,7 +146,7 @@ const H5RegisterPage: React.FC = () => {
</view> </view>
))} ))}
</view> </view>
<text className="trust-footer-text"></text> <text className="trust-footer-text">{t('trust_encryption')}</text>
</view> </view>
</view> </view>
); );

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
/** /**
* E2. H5页面 - + + * E2. H5页面 - + +
@ -27,7 +28,7 @@ export const SharePage: React.FC = () => {
fontSize: 13, fontSize: 13,
}}> }}>
<span style={{ fontSize: 16 }}>💎</span> <span style={{ fontSize: 16 }}>💎</span>
<span> Genex </span> <span>{t('share_from_genex')}</span>
</div> </div>
</div> </div>
@ -89,14 +90,14 @@ export const SharePage: React.FC = () => {
}}>8.5</span> }}>8.5</span>
</div> </div>
<div style={{ fontSize: 12, color: '#00C48C', marginTop: 6 }}> <div style={{ fontSize: 12, color: '#00C48C', marginTop: 6 }}>
$3.75 {t('coupon_price_save')} $3.75
</div> </div>
</div> </div>
{/* Info rows */} {/* Info rows */}
{[ {[
{ label: '有效期', value: '2026/12/31' }, { label: t('coupon_valid_until'), value: '2026/12/31' },
{ label: '使用门店', value: '全国 12,800+ 门店' }, { label: t('coupon_store'), value: t('coupon_stores_count') },
].map((item, i) => ( ].map((item, i) => (
<div key={i} style={{ <div key={i} style={{
display: 'flex', display: 'flex',
@ -126,7 +127,7 @@ export const SharePage: React.FC = () => {
cursor: 'pointer', cursor: 'pointer',
marginBottom: 12, marginBottom: 12,
}}> }}>
App {t('share_open_app')}
</button> </button>
<button style={{ <button style={{
width: '100%', width: '100%',
@ -139,7 +140,7 @@ export const SharePage: React.FC = () => {
fontWeight: 600, fontWeight: 600,
cursor: 'pointer', cursor: 'pointer',
}}> }}>
{t('share_miniapp_buy')}
</button> </button>
</div> </div>
</div> </div>
@ -164,13 +165,13 @@ export const ActivityPage: React.FC = () => {
alignItems: 'center', alignItems: 'center',
color: 'white', color: 'white',
}}> }}>
<h1 style={{ fontSize: 28, fontWeight: 700, margin: 0 }}></h1> <h1 style={{ fontSize: 28, fontWeight: 700, margin: 0 }}>{t('activity_new_user')}</h1>
<p style={{ fontSize: 16, opacity: 0.8, margin: '8px 0 0' }}> $10</p> <p style={{ fontSize: 16, opacity: 0.8, margin: '8px 0 0' }}>{t('activity_first_order')}</p>
</div> </div>
{/* Coupon Grid */} {/* Coupon Grid */}
<div style={{ padding: 20 }}> <div style={{ padding: 20 }}>
<h3 style={{ fontSize: 18, fontWeight: 600, marginBottom: 16 }}></h3> <h3 style={{ fontSize: 18, fontWeight: 600, marginBottom: 16 }}>{t('activity_coupons')}</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
{Array.from({ length: 4 }, (_, i) => ( {Array.from({ length: 4 }, (_, i) => (
<div key={i} style={{ <div key={i} style={{
@ -189,7 +190,7 @@ export const ActivityPage: React.FC = () => {
<span style={{ fontSize: 32, opacity: 0.4 }}>🎫</span> <span style={{ fontSize: 32, opacity: 0.4 }}>🎫</span>
</div> </div>
<div style={{ padding: 10 }}> <div style={{ padding: 10 }}>
<div style={{ fontSize: 12, color: '#5C6478' }}> {i + 1}</div> <div style={{ fontSize: 12, color: '#5C6478' }}>{t('activity_brand')} {i + 1}</div>
<div style={{ fontSize: 16, fontWeight: 700, color: '#6C5CE7' }}> <div style={{ fontSize: 16, fontWeight: 700, color: '#6C5CE7' }}>
${(i + 1) * 8.5} ${(i + 1) * 8.5}
</div> </div>
@ -214,7 +215,7 @@ export const ActivityPage: React.FC = () => {
fontWeight: 600, fontWeight: 600,
cursor: 'pointer', cursor: 'pointer',
}}> }}>
{t('activity_join_now')}
</button> </button>
</div> </div>
</div> </div>
@ -249,18 +250,18 @@ export const RegisterGuidePage: React.FC = () => {
</div> </div>
<h1 style={{ fontSize: 24, fontWeight: 700, color: '#141723', margin: '0 0 8px' }}> <h1 style={{ fontSize: 24, fontWeight: 700, color: '#141723', margin: '0 0 8px' }}>
Genex {t('register_join')}
</h1> </h1>
<p style={{ fontSize: 15, color: '#5C6478', margin: '0 0 40px', textAlign: 'center' }}> <p style={{ fontSize: 15, color: '#5C6478', margin: '0 0 40px', textAlign: 'center' }}>
<br /> {t('register_slogan')}<br />
{t('register_benefit')}
</p> </p>
{/* Features */} {/* Features */}
{[ {[
{ icon: '🎫', title: '海量优惠券', desc: '餐饮、购物、娱乐全覆盖' }, { icon: '🎫', title: t('register_benefit_coupons'), desc: t('register_benefit_coupons_desc') },
{ icon: '💰', title: '超值折扣', desc: '最低7折起省钱又省心' }, { icon: '💰', title: t('register_benefit_discount'), desc: t('register_benefit_discount_desc') },
{ icon: '🔒', title: '安全交易', desc: '平台担保,放心购买' }, { icon: '🔒', title: t('register_benefit_safe'), desc: t('register_benefit_safe_desc') },
].map((f, i) => ( ].map((f, i) => (
<div key={i} style={{ <div key={i} style={{
display: 'flex', display: 'flex',
@ -290,7 +291,7 @@ export const RegisterGuidePage: React.FC = () => {
fontSize: 16, fontWeight: 600, cursor: 'pointer', fontSize: 16, fontWeight: 600, cursor: 'pointer',
marginBottom: 12, marginBottom: 12,
}}> }}>
{t('register_now')}
</button> </button>
<button style={{ <button style={{
width: '100%', height: 52, width: '100%', height: 52,
@ -298,7 +299,7 @@ export const RegisterGuidePage: React.FC = () => {
border: '1.5px solid #6C5CE7', borderRadius: 12, border: '1.5px solid #6C5CE7', borderRadius: 12,
fontSize: 16, fontWeight: 600, cursor: 'pointer', fontSize: 16, fontWeight: 600, cursor: 'pointer',
}}> }}>
{t('register_login')}
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component (WeChat / Alipay) // Taro mini-program component (WeChat / Alipay)
/** /**
@ -15,7 +16,7 @@ const HomePage: React.FC = () => {
<view className="search-bar"> <view className="search-bar">
<view className="search-input"> <view className="search-input">
<text className="search-icon">🔍</text> <text className="search-icon">🔍</text>
<text className="search-placeholder">...</text> <text className="search-placeholder">{t('home_search_hint')}</text>
</view> </view>
</view> </view>
@ -27,11 +28,15 @@ const HomePage: React.FC = () => {
circular circular
indicatorActiveColor="#6C5CE7" indicatorActiveColor="#6C5CE7"
> >
{['新用户专享 - 首单立减¥10', '限时折扣 - 全场低至7折', '热门推荐 - 精选高折扣券'].map((text, i) => ( {[
{ title: t('home_banner_new_user'), subtitle: t('home_banner_new_user_desc') },
{ title: t('home_banner_flash'), subtitle: t('home_banner_flash_desc') },
{ title: t('home_banner_hot'), subtitle: t('home_banner_hot_desc') },
].map((banner, i) => (
<swiper-item key={i}> <swiper-item key={i}>
<view className={`banner-item banner-${i}`}> <view className={`banner-item banner-${i}`}>
<text className="banner-title">{text.split(' - ')[0]}</text> <text className="banner-title">{banner.title}</text>
<text className="banner-subtitle">{text.split(' - ')[1]}</text> <text className="banner-subtitle">{banner.subtitle}</text>
</view> </view>
</swiper-item> </swiper-item>
))} ))}
@ -40,13 +45,13 @@ const HomePage: React.FC = () => {
{/* Category Grid */} {/* Category Grid */}
<view className="category-grid"> <view className="category-grid">
{[ {[
{ name: '餐饮', icon: '🍽️' }, { name: t('home_category_food'), icon: '🍽️' },
{ name: '购物', icon: '🛍️' }, { name: t('home_category_shopping'), icon: '🛍️' },
{ name: '娱乐', icon: '🎮' }, { name: t('home_category_entertainment'), icon: '🎮' },
{ name: '出行', icon: '🚗' }, { name: t('home_category_travel'), icon: '🚗' },
{ name: '全部', icon: '📋' }, { name: t('all'), icon: '📋' },
].map(cat => ( ].map((cat, i) => (
<view key={cat.name} className="category-item"> <view key={i} className="category-item">
<view className="category-icon">{cat.icon}</view> <view className="category-icon">{cat.icon}</view>
<text className="category-name">{cat.name}</text> <text className="category-name">{cat.name}</text>
</view> </view>
@ -57,16 +62,16 @@ const HomePage: React.FC = () => {
<view className="ai-suggestion"> <view className="ai-suggestion">
<view className="ai-icon"></view> <view className="ai-icon"></view>
<view className="ai-content"> <view className="ai-content">
<text className="ai-title">AI </text> <text className="ai-title">{t('home_recommended')}</text>
<text className="ai-text"></text> <text className="ai-text">{t('home_ai_text')}</text>
</view> </view>
<text className="ai-arrow"></text> <text className="ai-arrow"></text>
</view> </view>
{/* Hot Coupons */} {/* Hot Coupons */}
<view className="section-header"> <view className="section-header">
<text className="section-title"></text> <text className="section-title">{t('home_hot')}</text>
<text className="section-more"> </text> <text className="section-more">{t('more')} </text>
</view> </view>
<view className="coupon-list"> <view className="coupon-list">

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component // Taro mini-program component
/** /**
@ -17,19 +18,19 @@ const LoginPage: React.FC = () => {
<text className="logo-text">G</text> <text className="logo-text">G</text>
</view> </view>
<text className="app-name">Genex</text> <text className="app-name">Genex</text>
<text className="app-slogan"></text> <text className="app-slogan">{t('app_slogan')}</text>
</view> </view>
{/* WeChat Login (小程序) */} {/* WeChat Login (小程序) */}
<view className="login-actions"> <view className="login-actions">
<view className="wechat-btn"> <view className="wechat-btn">
<text className="wechat-icon">💬</text> <text className="wechat-icon">💬</text>
<text className="wechat-text"></text> <text className="wechat-text">{t('login_wechat')}</text>
</view> </view>
<view className="divider"> <view className="divider">
<view className="divider-line" /> <view className="divider-line" />
<text className="divider-text"></text> <text className="divider-text">{t('login_or')}</text>
<view className="divider-line" /> <view className="divider-line" />
</view> </view>
@ -37,28 +38,28 @@ const LoginPage: React.FC = () => {
<view className="phone-section"> <view className="phone-section">
<view className="input-wrap"> <view className="input-wrap">
<text className="input-icon">📱</text> <text className="input-icon">📱</text>
<input className="input-field" placeholder="手机号" type="number" /> <input className="input-field" placeholder={t('login_phone')} type="number" />
</view> </view>
<view className="input-wrap code-wrap"> <view className="input-wrap code-wrap">
<view className="code-input"> <view className="code-input">
<text className="input-icon">🔒</text> <text className="input-icon">🔒</text>
<input className="input-field" placeholder="验证码" type="number" /> <input className="input-field" placeholder={t('login_code')} type="number" />
</view> </view>
<view className="code-btn"> <view className="code-btn">
<text className="code-btn-text"></text> <text className="code-btn-text">{t('login_send_code')}</text>
</view> </view>
</view> </view>
<view className="login-btn"> <view className="login-btn">
<text className="login-btn-text"></text> <text className="login-btn-text">{t('login_btn')}</text>
</view> </view>
</view> </view>
{/* Terms */} {/* Terms */}
<view className="terms"> <view className="terms">
<text className="terms-text"></text> <text className="terms-text">{t('login_agree')}</text>
<text className="terms-link"></text> <text className="terms-link">{`${t('login_user_agreement')}`}</text>
<text className="terms-text"></text> <text className="terms-text">{t('login_and')}</text>
<text className="terms-link"></text> <text className="terms-link">{`${t('login_privacy_policy')}`}</text>
</view> </view>
</view> </view>
</view> </view>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component // Taro mini-program component
/** /**
@ -9,7 +10,7 @@ import React from 'react';
*/ */
const MyCouponsPage: React.FC = () => { const MyCouponsPage: React.FC = () => {
const tabs = ['可使用', '已使用', '已过期']; const tabs = [t('my_coupons_available'), t('my_coupons_used'), t('my_coupons_expired')];
const [activeTab] = React.useState(0); const [activeTab] = React.useState(0);
return ( return (
@ -40,11 +41,11 @@ const MyCouponsPage: React.FC = () => {
<view className="coupon-center"> <view className="coupon-center">
<text className="coupon-brand">{coupon.brand}</text> <text className="coupon-brand">{coupon.brand}</text>
<text className="coupon-name">{coupon.name}</text> <text className="coupon-name">{coupon.name}</text>
<text className="coupon-expiry"> {coupon.expiry}</text> <text className="coupon-expiry">{t('coupon_valid_until_prefix')} {coupon.expiry}</text>
</view> </view>
<view className="coupon-right"> <view className="coupon-right">
<view className="use-btn"> <view className="use-btn">
<text className="use-btn-text">使</text> <text className="use-btn-text">{t('coupon_use')}</text>
</view> </view>
</view> </view>
{/* Ticket notch decoration */} {/* Ticket notch decoration */}
@ -58,7 +59,7 @@ const MyCouponsPage: React.FC = () => {
{activeTab > 0 && ( {activeTab > 0 && (
<view className="empty-state"> <view className="empty-state">
<text className="empty-icon">📭</text> <text className="empty-icon">📭</text>
<text className="empty-text"></text> <text className="empty-text">{t('my_coupons_empty')}</text>
</view> </view>
)} )}
</view> </view>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component // Taro mini-program component
/** /**
@ -18,7 +19,7 @@ const ProfilePage: React.FC = () => {
<view className="user-info"> <view className="user-info">
<text className="username">User_138****88</text> <text className="username">User_138****88</text>
<view className="kyc-badge"> <view className="kyc-badge">
<text className="kyc-text">L1 </text> <text className="kyc-text">{`L1 ${t('profile_kyc_basic')}`}</text>
</view> </view>
</view> </view>
</view> </view>
@ -26,11 +27,11 @@ const ProfilePage: React.FC = () => {
{/* Stats */} {/* Stats */}
<view className="stats-row"> <view className="stats-row">
{[ {[
{ value: '5', label: '持有券' }, { value: '5', label: t('profile_owned') },
{ value: '12', label: '已使用' }, { value: '12', label: t('my_coupons_used') },
{ value: '3', label: '已过期' }, { value: '3', label: t('my_coupons_expired') },
].map(s => ( ].map((s, i) => (
<view key={s.label} className="stat-item"> <view key={i} className="stat-item">
<text className="stat-value">{s.value}</text> <text className="stat-value">{s.value}</text>
<text className="stat-label">{s.label}</text> <text className="stat-label">{s.label}</text>
</view> </view>
@ -40,12 +41,12 @@ const ProfilePage: React.FC = () => {
{/* Menu */} {/* Menu */}
<view className="menu-section"> <view className="menu-section">
{[ {[
{ icon: '🎫', label: '我的券', path: '/pages/my-coupons/index' }, { icon: '🎫', label: t('my_coupons'), path: '/pages/my-coupons/index' },
{ icon: '📋', label: '我的订单', path: '/pages/orders/index' }, { icon: '📋', label: t('profile_orders'), path: '/pages/orders/index' },
{ icon: '💳', label: '支付管理', path: '' }, { icon: '💳', label: t('profile_payment'), path: '' },
{ icon: '🔔', label: '消息通知', path: '' }, { icon: '🔔', label: t('profile_notifications'), path: '' },
].map(item => ( ].map((item, i) => (
<view key={item.label} className="menu-item"> <view key={i} className="menu-item">
<text className="menu-icon">{item.icon}</text> <text className="menu-icon">{item.icon}</text>
<text className="menu-label">{item.label}</text> <text className="menu-label">{item.label}</text>
<text className="menu-arrow"></text> <text className="menu-arrow"></text>
@ -55,12 +56,12 @@ const ProfilePage: React.FC = () => {
<view className="menu-section"> <view className="menu-section">
{[ {[
{ icon: '🌐', label: '语言 / Language', value: '简体中文' }, { icon: '🌐', label: t('profile_language'), value: '简体中文' },
{ icon: '💰', label: '货币', value: 'USD' }, { icon: '💰', label: t('profile_currency'), value: 'USD' },
{ icon: '❓', label: '帮助中心', value: '' }, { icon: '❓', label: t('profile_help'), value: '' },
{ icon: '⚙️', label: '设置', value: '' }, { icon: '⚙️', label: t('profile_settings'), value: '' },
].map(item => ( ].map((item, i) => (
<view key={item.label} className="menu-item"> <view key={i} className="menu-item">
<text className="menu-icon">{item.icon}</text> <text className="menu-icon">{item.icon}</text>
<text className="menu-label">{item.label}</text> <text className="menu-label">{item.label}</text>
{item.value ? <text className="menu-value">{item.value}</text> : null} {item.value ? <text className="menu-value">{item.value}</text> : null}
@ -72,11 +73,11 @@ const ProfilePage: React.FC = () => {
{/* Download App Banner */} {/* Download App Banner */}
<view className="download-banner"> <view className="download-banner">
<view className="download-content"> <view className="download-content">
<text className="download-title"> Genex App</text> <text className="download-title">{t('download_app_title')}</text>
<text className="download-desc">P2P转赠等完整功能</text> <text className="download-desc">{t('download_app_full_desc')}</text>
</view> </view>
<view className="download-btn"> <view className="download-btn">
<text className="download-btn-text"></text> <text className="download-btn-text">{t('download_btn')}</text>
</view> </view>
</view> </view>
</view> </view>

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { t } from '@/i18n';
// Taro mini-program component // Taro mini-program component
/** /**
@ -13,7 +14,7 @@ const RedeemPage: React.FC = () => {
{/* Coupon Info */} {/* Coupon Info */}
<view className="coupon-info"> <view className="coupon-info">
<text className="coupon-name"> ¥25 </text> <text className="coupon-name"> ¥25 </text>
<text className="coupon-value"> ¥25.00</text> <text className="coupon-value">{t('coupon_face_value')} ¥25.00</text>
</view> </view>
{/* QR Code */} {/* QR Code */}
@ -21,7 +22,7 @@ const RedeemPage: React.FC = () => {
<view className="qr-box"> <view className="qr-box">
<view className="qr-placeholder"> <view className="qr-placeholder">
<text className="qr-icon">📱</text> <text className="qr-icon">📱</text>
<text className="qr-label"></text> <text className="qr-label">{t('redeem_qr_label')}</text>
</view> </view>
</view> </view>
@ -33,18 +34,18 @@ const RedeemPage: React.FC = () => {
{/* Countdown */} {/* Countdown */}
<view className="countdown"> <view className="countdown">
<text className="countdown-icon"></text> <text className="countdown-icon"></text>
<text className="countdown-text"> 04:58</text> <text className="countdown-text">{t('redeem_valid_time')} 04:58</text>
</view> </view>
<view className="refresh-btn"> <view className="refresh-btn">
<text className="refresh-text"></text> <text className="refresh-text">{t('redeem_refresh')}</text>
</view> </view>
</view> </view>
{/* Hint */} {/* Hint */}
<view className="hint-box"> <view className="hint-box">
<text className="hint-icon"></text> <text className="hint-icon"></text>
<text className="hint-text"></text> <text className="hint-text">{t('redeem_hint')}</text>
</view> </view>
</view> </view>
); );

View File

@ -1,302 +1,62 @@
/// Genex Mobile App - i18n import 'package:flutter/material.dart';
import 'strings/zh_cn.dart';
import 'strings/zh_tw.dart';
import 'strings/en.dart';
import 'strings/ja.dart';
/// Genex
/// ///
/// : zh-CN (), en-US, ja-JP /// 使: context.t('key') AppLocalizations.of(context).get('key')
/// 使: AppLocalizations.of(context).translate('key') /// : zh_CN(), zh_TW, en, ja
class AppLocalizations { class AppLocalizations {
final String locale; final Locale locale;
late final Map<String, String> _strings;
AppLocalizations(this.locale); AppLocalizations(this.locale) {
final key = locale.countryCode != null && locale.countryCode!.isNotEmpty
? '${locale.languageCode}_${locale.countryCode}'
: locale.languageCode;
static AppLocalizations of(dynamic context) { _strings = _allStrings[key] ?? _allStrings[locale.languageCode] ?? zhCN;
// In production, obtain from InheritedWidget / Provider
return AppLocalizations('zh-CN');
} }
String translate(String key) { static AppLocalizations of(BuildContext context) {
return _localizedValues[locale]?[key] ?? return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
_localizedValues['zh-CN']?[key] ??
key;
} }
// Shorthand /// fallback key
String t(String key) => translate(key); String get(String key) => _strings[key] ?? zhCN[key] ?? key;
static const supportedLocales = ['zh-CN', 'en-US', 'ja-JP']; static const Map<String, Map<String, String>> _allStrings = {
'zh': zhCN,
static const Map<String, Map<String, String>> _localizedValues = { 'zh_CN': zhCN,
'zh-CN': _zhCN, 'zh_TW': zhTW,
'en-US': _enUS, 'en': en,
'ja-JP': _jaJP, 'ja': ja,
};
static const Map<String, String> _zhCN = {
// Common
'app_name': 'Genex',
'confirm': '确认',
'cancel': '取消',
'save': '保存',
'delete': '删除',
'edit': '编辑',
'search': '搜索',
'loading': '加载中...',
'retry': '重试',
'done': '完成',
'next': '下一步',
'back': '返回',
'close': '关闭',
'more': '更多',
'all': '全部',
// Tabs
'tab_home': '首页',
'tab_market': '市场',
'tab_wallet': '钱包',
'tab_profile': '我的',
// Home
'home_greeting': '你好',
'home_search_hint': '搜索券、品牌...',
'home_recommended': 'AI推荐',
'home_hot': '热门券',
'home_new': '新上架',
'home_categories': '分类浏览',
// Coupon
'coupon_buy': '购买',
'coupon_sell': '出售',
'coupon_transfer': '转赠',
'coupon_use': '使用',
'coupon_detail': '券详情',
'coupon_face_value': '面值',
'coupon_price': '价格',
'coupon_discount': '折扣',
'coupon_valid_until': '有效期至',
'coupon_brand': '品牌',
'coupon_category': '类别',
'coupon_my_coupons': '我的券',
'coupon_available': '可用',
'coupon_used': '已使用',
'coupon_expired': '已过期',
// Trading
'trade_buy_order': '买单',
'trade_sell_order': '卖单',
'trade_price_input': '输入价格',
'trade_quantity': '数量',
'trade_total': '合计',
'trade_history': '交易记录',
'trade_pending': '待成交',
'trade_completed': '已完成',
// Wallet
'wallet_balance': '余额',
'wallet_deposit': '充值',
'wallet_withdraw': '提现',
'wallet_transactions': '交易记录',
// Profile
'profile_settings': '设置',
'profile_kyc': '身份认证',
'profile_kyc_l0': '未认证',
'profile_kyc_l1': 'L1 基础认证',
'profile_kyc_l2': 'L2 身份认证',
'profile_kyc_l3': 'L3 高级认证',
'profile_language': '语言',
'profile_currency': '货币',
'profile_help': '帮助中心',
'profile_about': '关于',
'profile_logout': '退出登录',
'profile_pro_mode': '高级模式',
// Payment
'payment_method': '支付方式',
'payment_confirm': '确认支付',
'payment_success': '支付成功',
// AI
'ai_assistant': 'AI助手',
'ai_ask': '问我任何问题...',
'ai_suggestion': 'AI建议',
};
static const Map<String, String> _enUS = {
// Common
'app_name': 'Genex',
'confirm': 'Confirm',
'cancel': 'Cancel',
'save': 'Save',
'delete': 'Delete',
'edit': 'Edit',
'search': 'Search',
'loading': 'Loading...',
'retry': 'Retry',
'done': 'Done',
'next': 'Next',
'back': 'Back',
'close': 'Close',
'more': 'More',
'all': 'All',
// Tabs
'tab_home': 'Home',
'tab_market': 'Market',
'tab_wallet': 'Wallet',
'tab_profile': 'Profile',
// Home
'home_greeting': 'Hello',
'home_search_hint': 'Search coupons, brands...',
'home_recommended': 'AI Picks',
'home_hot': 'Trending',
'home_new': 'New Arrivals',
'home_categories': 'Categories',
// Coupon
'coupon_buy': 'Buy',
'coupon_sell': 'Sell',
'coupon_transfer': 'Gift',
'coupon_use': 'Redeem',
'coupon_detail': 'Coupon Details',
'coupon_face_value': 'Face Value',
'coupon_price': 'Price',
'coupon_discount': 'Discount',
'coupon_valid_until': 'Valid Until',
'coupon_brand': 'Brand',
'coupon_category': 'Category',
'coupon_my_coupons': 'My Coupons',
'coupon_available': 'Available',
'coupon_used': 'Used',
'coupon_expired': 'Expired',
// Trading
'trade_buy_order': 'Buy Order',
'trade_sell_order': 'Sell Order',
'trade_price_input': 'Enter Price',
'trade_quantity': 'Quantity',
'trade_total': 'Total',
'trade_history': 'Trade History',
'trade_pending': 'Pending',
'trade_completed': 'Completed',
// Wallet
'wallet_balance': 'Balance',
'wallet_deposit': 'Deposit',
'wallet_withdraw': 'Withdraw',
'wallet_transactions': 'Transactions',
// Profile
'profile_settings': 'Settings',
'profile_kyc': 'Verification',
'profile_kyc_l0': 'Unverified',
'profile_kyc_l1': 'L1 Basic',
'profile_kyc_l2': 'L2 Identity',
'profile_kyc_l3': 'L3 Advanced',
'profile_language': 'Language',
'profile_currency': 'Currency',
'profile_help': 'Help Center',
'profile_about': 'About',
'profile_logout': 'Log Out',
'profile_pro_mode': 'Pro Mode',
// Payment
'payment_method': 'Payment Method',
'payment_confirm': 'Confirm Payment',
'payment_success': 'Payment Successful',
// AI
'ai_assistant': 'AI Assistant',
'ai_ask': 'Ask me anything...',
'ai_suggestion': 'AI Suggestion',
};
static const Map<String, String> _jaJP = {
// Common
'app_name': 'Genex',
'confirm': '確認',
'cancel': 'キャンセル',
'save': '保存',
'delete': '削除',
'edit': '編集',
'search': '検索',
'loading': '読み込み中...',
'retry': 'リトライ',
'done': '完了',
'next': '次へ',
'back': '戻る',
'close': '閉じる',
'more': 'もっと見る',
'all': 'すべて',
// Tabs
'tab_home': 'ホーム',
'tab_market': 'マーケット',
'tab_wallet': 'ウォレット',
'tab_profile': 'マイページ',
// Home
'home_greeting': 'こんにちは',
'home_search_hint': 'クーポン、ブランドを検索...',
'home_recommended': 'AIおすすめ',
'home_hot': '人気',
'home_new': '新着',
'home_categories': 'カテゴリー',
// Coupon
'coupon_buy': '購入',
'coupon_sell': '売却',
'coupon_transfer': '贈与',
'coupon_use': '使用',
'coupon_detail': 'クーポン詳細',
'coupon_face_value': '額面',
'coupon_price': '価格',
'coupon_discount': '割引',
'coupon_valid_until': '有効期限',
'coupon_brand': 'ブランド',
'coupon_category': 'カテゴリー',
'coupon_my_coupons': 'マイクーポン',
'coupon_available': '利用可能',
'coupon_used': '使用済み',
'coupon_expired': '期限切れ',
// Trading
'trade_buy_order': '買い注文',
'trade_sell_order': '売り注文',
'trade_price_input': '価格を入力',
'trade_quantity': '数量',
'trade_total': '合計',
'trade_history': '取引履歴',
'trade_pending': '未約定',
'trade_completed': '約定済み',
// Wallet
'wallet_balance': '残高',
'wallet_deposit': '入金',
'wallet_withdraw': '出金',
'wallet_transactions': '取引履歴',
// Profile
'profile_settings': '設定',
'profile_kyc': '本人確認',
'profile_kyc_l0': '未確認',
'profile_kyc_l1': 'L1 基本認証',
'profile_kyc_l2': 'L2 身分認証',
'profile_kyc_l3': 'L3 高度認証',
'profile_language': '言語',
'profile_currency': '通貨',
'profile_help': 'ヘルプ',
'profile_about': 'アプリについて',
'profile_logout': 'ログアウト',
'profile_pro_mode': 'プロモード',
// Payment
'payment_method': '支払い方法',
'payment_confirm': '支払いを確認',
'payment_success': '支払い完了',
// AI
'ai_assistant': 'AIアシスタント',
'ai_ask': '何でも聞いてください...',
'ai_suggestion': 'AIの提案',
}; };
} }
/// LocalizationsDelegate
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) {
return ['zh', 'en', 'ja'].contains(locale.languageCode);
}
@override
Future<AppLocalizations> load(Locale locale) async {
return AppLocalizations(locale);
}
@override
bool shouldReload(covariant LocalizationsDelegate<AppLocalizations> old) =>
false;
}
/// BuildContext 访
extension AppLocalizationsExtension on BuildContext {
/// : context.t('key')
String t(String key) => AppLocalizations.of(this).get(key);
}

View File

@ -0,0 +1,686 @@
const Map<String, String> en = {
// ============ Common ============
'common.confirm': 'Confirm',
'common.cancel': 'Cancel',
'common.save': 'Save',
'common.delete': 'Delete',
'common.edit': 'Edit',
'common.search': 'Search',
'common.loading': 'Loading...',
'common.retry': 'Retry',
'common.done': 'Done',
'common.next': 'Next',
'common.back': 'Back',
'common.close': 'Close',
'common.more': 'More',
'common.all': 'All',
'common.filter': 'Filter',
'common.sort': 'Sort',
'common.copy': 'Copy',
'common.copied': 'Copied to clipboard',
'common.today': 'Today',
'common.thisWeek': 'This Week',
'common.thisMonth': 'This Month',
// ============ Navigation ============
'nav.home': 'Home',
'nav.market': 'Market',
'nav.myCoupons': 'My Coupons',
'nav.messages': 'Messages',
'nav.profile': 'Profile',
// ============ Welcome / Auth ============
'welcome.slogan': 'Make Every Coupon Count',
'welcome.phoneRegister': 'Phone Sign Up',
'welcome.emailRegister': 'Email Sign Up',
'welcome.otherLogin': 'Other Login Methods',
'welcome.hasAccount': 'Already have an account?',
'welcome.login': 'Log In',
'welcome.agreement': 'By signing up you agree to Terms of Service and Privacy Policy',
'login.title': 'Welcome Back',
'login.subtitle': 'Log in to manage your coupon assets',
'login.passwordTab': 'Password',
'login.codeTab': 'Verification Code',
'login.phoneOrEmail': 'Phone or Email',
'login.password': 'Password',
'login.forgotPassword': 'Forgot password?',
'login.submit': 'Log In',
'login.phone': 'Phone',
'login.verifyCode': 'Code',
'login.getCode': 'Get Code',
'register.title': 'Create Account',
'register.emailSubtitle': 'Sign up with your email',
'register.phoneSubtitle': 'Sign up with your phone number',
'register.email': 'Email',
'register.phone': 'Phone',
'register.emailHint': 'Enter your email',
'register.phoneHint': 'Enter your phone number',
'register.code': 'Code',
'register.codeHint': 'Enter 6-digit code',
'register.getCode': 'Get Code',
'register.setPassword': 'Set Password',
'register.passwordHint': '8-20 chars, letters and numbers',
'register.agreement': 'I have read and agree to',
'register.userAgreement': 'Terms of Service',
'register.privacyPolicy': 'Privacy Policy',
'register.submit': 'Sign Up',
'register.stepVerify': 'Verify',
'register.stepPassword': 'Password',
'register.stepDone': 'Done',
'register.rule8chars': '8+ chars',
'register.ruleLetter': 'Letters',
'register.ruleNumber': 'Numbers',
'forgot.title': 'Reset Password',
'forgot.inputAccount': 'Enter phone or email',
'forgot.sendHint': 'We\'ll send you a verification code',
'forgot.accountHint': 'Phone / Email',
'forgot.getCode': 'Get Code',
'forgot.inputCode': 'Enter Code',
'forgot.codeSentTo': 'Code sent to',
'forgot.codeHint': '6-digit code',
'forgot.resend': 'Resend',
'forgot.next': 'Next',
'forgot.setNewPassword': 'Set New Password',
'forgot.newPasswordHint': 'Enter new password (8+ chars)',
'forgot.newPassword': 'New Password',
'forgot.confirmPassword': 'Confirm Password',
'forgot.confirmChange': 'Confirm',
'forgot.success': 'Password Changed',
'forgot.successHint': 'Please log in with your new password',
'forgot.backToLogin': 'Back to Login',
// ============ Home ============
'home.searchHint': 'Search coupons, brands, categories...',
'home.dining': 'Dining',
'home.shopping': 'Shopping',
'home.entertainment': 'Entertainment',
'home.travel': 'Travel',
'home.lifestyle': 'Lifestyle',
'home.brand': 'Brands',
'home.discount': 'Deals',
'home.allCategories': 'All',
'home.featuredCoupons': 'Featured Coupons',
'home.viewAllCoupons': 'View All',
'home.aiRecommend': 'AI Picks',
'home.aiRecommendDesc': 'Found 3 great deals based on your preferences',
'home.bannerNewUser': 'New User Special',
'home.bannerNewUserDesc': 'Save \$10 on first order',
'home.bannerDiscount': 'Flash Sale',
'home.bannerDiscountDesc': 'Up to 30% off everything',
'home.bannerHot': 'Hot Picks',
'home.bannerHotDesc': 'Top discount coupons',
// ============ Market ============
'market.title': 'Marketplace',
'market.primary': 'Primary Market (New)',
'market.secondary': 'Secondary Market (Resale)',
'market.dining': 'Dining',
'market.shopping': 'Shopping',
'market.entertainment': 'Entertainment',
'market.travel': 'Travel',
'market.lifestyle': 'Lifestyle',
'market.sports': 'Sports',
'market.discountRate': 'Discount',
'market.priceUp': 'Price \u2191',
'market.priceDown': 'Price \u2193',
'market.expiryDate': 'Expiry',
'market.issuePrice': 'Issue Price',
'market.faceValue': 'Face Value',
'market.discount': 'Discount',
'market.totalSupply': 'Supply',
'market.salesProgress': 'Sales Progress',
'market.upcoming': 'Upcoming',
'market.subscribing': 'Live',
'market.ended': 'Ended',
'market.timeToStart': 'Starts in',
'market.couponBrand': 'Coupon / Brand',
'market.latestPrice': 'Price',
'market.change24h': '24h Change',
'market.discountSuffix': ' off',
// ============ My Coupons ============
'myCoupons.title': 'My Coupons',
'myCoupons.usable': 'Active',
'myCoupons.pendingRedeem': 'Pending',
'myCoupons.expired': 'Expired',
'myCoupons.faceValue': 'Value',
'myCoupons.transfer': 'Gift',
'myCoupons.sell': 'Sell',
'myCoupons.expiredText': 'Expired',
'myCoupons.expiringToday': 'Expires today',
'myCoupons.daysToExpiry': 'd left',
// ============ Coupon Detail ============
'couponDetail.title': 'Coupon Details',
'couponDetail.favorite': 'Favorite',
'couponDetail.buyNow': 'Buy Now',
'couponDetail.saveBadge': 'Save',
'couponDetail.faceValue': 'Face Value',
'couponDetail.validUntil': 'Valid Until',
'couponDetail.type': 'Type',
'couponDetail.issuer': 'Issuer',
'couponDetail.consumeCoupon': 'Voucher',
'couponDetail.usageNote': 'Usage Notes',
'couponDetail.allStores': 'Valid at all nationwide stores',
'couponDetail.canTransfer': 'Can be gifted to others',
'couponDetail.useAnytime': 'Use anytime before expiry',
'couponDetail.noStack': 'Cannot be combined',
'couponDetail.noCash': 'Not redeemable for cash',
'couponDetail.stores': 'Stores',
'couponDetail.storeCount': '12,800+ Stores Nationwide',
'couponDetail.storeDesc': 'Valid at all official stores nationwide',
'couponDetail.priceTrend': 'Price Trend',
'couponDetail.last30Days': 'Last 30 Days',
'couponDetail.highest': 'High',
'couponDetail.lowest': 'Low',
'couponDetail.average': 'Avg',
'couponDetail.tradeHistory': 'Trade History',
'couponDetail.nearbyStores': 'Nearby Stores',
'couponDetail.distance': 'Distance',
'couponDetail.open': 'Open',
'couponDetail.similar': 'Similar Coupons',
// ============ My Coupon Detail ============
'myCoupon.title': 'My Coupon',
'myCoupon.active': 'Active',
'myCoupon.showQrHint': 'Show this QR code to the merchant to redeem',
'myCoupon.switchBarcode': 'Switch to Barcode',
'myCoupon.faceValue': 'Face Value',
'myCoupon.purchasePrice': 'Purchase Price',
'myCoupon.validUntil': 'Valid Until',
'myCoupon.orderNo': 'Order No.',
'myCoupon.resellCount': 'Resale Remaining',
'myCoupon.transfer': 'Gift',
'myCoupon.sell': 'Sell',
'myCoupon.usageNote': 'Usage Notes',
'myCoupon.useInStore': 'Valid at all nationwide stores',
'myCoupon.useInTime': 'Use before expiry date',
'myCoupon.onePerVisit': 'One coupon per visit',
'myCoupon.noCash': 'Not redeemable for cash',
'myCoupon.extractToWallet': 'Withdraw to External Wallet',
'myCoupon.requireKycL2': 'Requires KYC L2+',
'myCoupon.viewTrades': 'View Trade Records',
'myCoupon.help': 'Help',
// ============ Order Confirm ============
'orderConfirm.title': 'Confirm Order',
'orderConfirm.quantity': 'Quantity',
'orderConfirm.paymentMethod': 'Payment Method',
'orderConfirm.bankCard': 'Bank/Credit Card',
'orderConfirm.priceDetail': 'Price Details',
'orderConfirm.buyingNote': 'You are purchasing a voucher for consumption',
'orderConfirm.total': 'Total',
'orderConfirm.confirmPay': 'Confirm Payment',
'orderConfirm.unitPrice': 'Unit Price',
'orderConfirm.count': 'Qty',
'orderConfirm.saveBadge': 'Savings vs Face Value',
'orderConfirm.biometricHint': 'Verify with fingerprint or face to complete payment',
'orderConfirm.usePasswordPay': 'Use Password Instead',
// ============ Payment ============
'payment.title': 'Select Payment Method',
'payment.addNew': 'Add New Payment Method',
'payment.confirmPay': 'Confirm Payment',
'payment.bankTransfer': 'Bank Transfer',
'paymentSuccess.title': 'Payment Successful',
'paymentSuccess.hint': 'Coupon added to your holdings',
'paymentSuccess.couponName': 'Coupon',
'paymentSuccess.payAmount': 'Amount Paid',
'paymentSuccess.orderNo': 'Order No.',
'paymentSuccess.payTime': 'Payment Time',
'paymentSuccess.viewMyCoupon': 'View My Coupons',
'paymentSuccess.continueBrowse': 'Continue Browsing',
// ============ Search ============
'search.hint': 'Search coupons, brands, categories...',
'search.cancel': 'Cancel',
'search.hotSearch': 'Trending Searches',
'search.history': 'Search History',
'search.clear': 'Clear',
'search.diningCoupon': 'Dining',
'search.discountCoupon': 'Discount',
'search.travel': 'Travel',
// ============ Redeem ============
'redeem.title': 'Show Code',
'redeem.faceValue': 'Face Value',
'redeem.validTime': 'Valid Time',
'redeem.refresh': 'Refresh Code',
'redeem.showHint': 'Show this code to merchant. Screen brightness auto-maximized.',
// ============ Sell Order ============
'sellOrder.title': 'List for Sale',
'sellOrder.faceValue': 'Face Value',
'sellOrder.credit': 'Rating',
'sellOrder.setPrice': 'Set Price',
'sellOrder.price': 'Price',
'sellOrder.aiSuggest': 'AI Suggested Price',
'sellOrder.bestDealRate': 'Best chance of selling',
'sellOrder.discountRate': 'Discount',
'sellOrder.platformFee': 'Platform Fee (1.5%)',
'sellOrder.estimatedReceive': 'Est. Receive',
'sellOrder.marketAvg': 'Market Average',
'sellOrder.recent24hTrades': '24h Trades',
'sellOrder.tradesUnit': ' trades',
'sellOrder.confirmList': 'Confirm Listing',
'sellOrder.success': 'Listed Successfully',
'sellOrder.successHint': 'Your coupon is now on the market. It will be auto-matched when a buyer places an order.',
'sellOrder.ok': 'OK',
// ============ Trading Page ============
'tradingPage.title': 'My Trades',
'tradingPage.pendingOrders': 'Open Orders',
'tradingPage.tradeRecords': 'Trade History',
'tradingPage.listPrice': 'List Price',
'tradingPage.listTime': 'Listed',
'tradingPage.cancelOrder': 'Cancel',
'tradingPage.buy': 'Buy',
'tradingPage.sell': 'Sell',
// ============ Transfer ============
'transfer.title': 'Gift to Friend',
'transfer.searchFriend': 'Search friend (phone/username)',
'transfer.confirmTransfer': 'Confirm Transfer',
'transfer.success': 'Transfer Successful',
'transfer.confirm': 'OK',
'transfer.cancel': 'Cancel',
// ============ Wallet ============
'wallet.myBalance': 'My Balance',
'wallet.totalBalance': 'Total Balance',
'wallet.withdrawable': 'Withdrawable',
'wallet.frozen': 'Frozen',
'wallet.deposit': 'Deposit',
'wallet.withdraw': 'Withdraw',
'wallet.records': 'Records',
'wallet.filter': 'Filter',
'wallet.buyIn': 'Buy',
'wallet.sellOut': 'Sell',
'wallet.giftTransfer': 'Gift',
'wallet.redeemUse': 'Redeem',
'deposit.title': 'Deposit',
'deposit.currentBalance': 'Current Balance',
'deposit.amount': 'Amount',
'deposit.custom': 'Custom Amount',
'deposit.paymentMethod': 'Payment Method',
'deposit.submit': 'Deposit',
'withdraw.title': 'Withdraw',
'withdraw.availableBalance': 'Available Balance',
'withdraw.amount': 'Amount',
'withdraw.all': 'All',
'withdraw.to': 'Withdraw to',
'withdraw.savingsAccount': 'Savings Account',
'withdraw.fee': 'Fee (0.5%)',
'withdraw.actualReceive': 'You\'ll Receive',
'withdraw.estimateTime': 'Estimated 1-2 business days',
'withdraw.submit': 'Confirm Withdrawal',
'txRecords.title': 'Transaction Records',
'txRecords.all': 'All',
'txRecords.buy': 'Buy',
'txRecords.sell': 'Sell',
'txRecords.transfer': 'Gift',
'txRecords.noRecords': 'No records',
'txRecords.orderNo': 'Order No.',
'txRecords.transferTo': 'Gifted to',
// ============ Profile ============
'profile.favorites': 'Favorites',
'profile.orders': 'Orders',
'profile.coupons': 'Coupons',
'profile.wallet': 'Wallet',
'profile.account': 'Account',
'profile.trade': 'Trades',
'profile.settings': 'Settings',
'profile.holdCoupons': 'Held',
'profile.saved': 'Saved',
'profile.credit': 'Credit',
'profile.creditScore': 'Credit Score',
'profile.myTrades': 'My Trades',
'profile.walletBalance': 'Balance',
'profile.paymentManage': 'Payment',
'profile.kyc': 'Verification',
'profile.proMode': 'Pro Mode',
'profile.myFavorites': 'My Favorites',
'profile.securitySettings': 'Security',
'profile.advancedSettings': 'Advanced',
'profile.aboutGenex': 'About Genex',
'profile.helpCenter': 'Help',
'profile.issuerPortal': 'Issuer Portal',
'profile.merchantPortal': 'Merchant Portal',
'profile.simplifiedChinese': 'Simplified Chinese',
'profile.logout': 'Log Out',
// ============ Settings ============
'settings.title': 'Settings',
'settings.accountSecurity': 'Account & Security',
'settings.phone': 'Phone',
'settings.email': 'Email',
'settings.changePassword': 'Change Password',
'settings.identity': 'Verification',
'settings.paymentManage': 'Payment Management',
'settings.paymentMethod': 'Payment Methods',
'settings.bankAccount': 'Bank Account',
'settings.paymentPassword': 'Payment Password',
'settings.notifications': 'Notifications',
'settings.tradeNotify': 'Trade Alerts',
'settings.expiryRemind': 'Expiry Reminders',
'settings.marketChange': 'Market Updates',
'settings.marketingPush': 'Promotions',
'settings.general': 'General',
'settings.language': 'Language',
'settings.currency': 'Currency',
'settings.clearCache': 'Clear Cache',
'settings.about': 'About',
'settings.version': 'Version',
'settings.userAgreement': 'Terms of Service',
'settings.privacyPolicy': 'Privacy Policy',
'settings.helpCenter': 'Help Center',
'settings.logout': 'Log Out',
'settings.selectCurrency': 'Select Currency',
'settings.currencyNote': 'This affects currency display in all trading pages',
'settings.selectLanguage': 'Select Language',
'settings.currencySymbol': 'Symbol',
// ============ KYC ============
'kyc.title': 'Verification',
'kyc.currentLevel': 'Current Level',
'kyc.l1Title': 'L1 Basic',
'kyc.l1Desc': 'Phone + Email verification',
'kyc.l1Limit': 'Daily limit \$500',
'kyc.l1Feature': 'Buy coupons, redeem at stores',
'kyc.l2Title': 'L2 Identity',
'kyc.l2Desc': 'ID / Passport verification',
'kyc.l2Limit': 'Daily limit \$5,000',
'kyc.l2Feature': 'Unlock trading & P2P transfers',
'kyc.l3Title': 'L3 Advanced',
'kyc.l3Desc': 'Video review + Proof of address',
'kyc.l3Limit': 'No limit',
'kyc.l3Feature': 'Unlimited trading & withdrawals',
'kyc.completed': 'Completed',
'kyc.goVerify': 'Verify',
'kyc.badgeLabel': 'Verified',
// ============ Payment Management ============
'payManage.title': 'Payment Management',
'payManage.myCards': 'My Cards',
'payManage.addCard': 'Add New Card',
'payManage.bankAccount': 'Bank Account (for withdrawals)',
'payManage.paymentSecurity': 'Payment Security',
'payManage.paymentPassword': 'Payment Password',
'payManage.passwordSet': 'Set',
'payManage.biometricPay': 'Biometric Payment',
'payManage.biometricEnabled': 'Enabled',
'payManage.noPasswordPay': 'Quick Pay',
'payManage.noPasswordLimit': 'Up to \$10/txn',
// ============ AI Chat ============
'aiChat.title': 'AI Assistant',
'aiChat.greeting': 'Hi! I\'m Genex AI Assistant. I can help you find great deals, compare prices, and recommend combos. Try asking:',
'aiChat.suggest1': 'Recommend coupons for me',
'aiChat.suggest2': 'Is Starbucks coupon worth buying?',
'aiChat.suggest3': 'Help me compare prices',
'aiChat.suggest4': 'My coupon is expiring soon',
'aiChat.inputHint': 'Ask me anything about coupons...',
'aiChat.confirmAction': 'Confirm',
'aiChat.riskLow': 'Low Risk',
'aiChat.riskNormal': 'Confirm',
'aiChat.riskHigh': 'High Risk',
// ============ AI Fab ============
'aiFab.title': 'AI Assistant',
'aiFab.greeting': 'Hi! I\'m Genex AI Assistant. I can help you manage coupon assets, find deals, and analyze prices. How can I help?',
'aiFab.inputHint': 'Type a message...',
'aiFab.suggest1': 'Find high-discount coupons',
'aiFab.suggest2': 'Are my coupons expiring soon?',
'aiFab.suggest3': 'Recommend today\'s best deals',
'aiFab.suggest4': 'Analyze my coupon portfolio',
// ============ Messages ============
'message.title': 'Messages',
'message.markAllRead': 'Mark All Read',
'message.tabTrade': 'Trades',
'message.tabExpiry': 'Expiry',
'message.tabAnnouncement': 'Notices',
'message.detailTitle': 'Message Details',
'message.tradeNotify': 'Trade Alert',
'message.expiryRemind': 'Expiry Reminder',
'message.systemNotify': 'System Notice',
'message.promoNotify': 'Promotion',
'message.tradeSuccess': 'Trade Successful',
'message.couponName': 'Coupon',
'message.faceValue': 'Face Value',
'message.payAmount': 'Amount Paid',
'message.orderNo': 'Order No.',
'message.payMethod': 'Payment Method',
'message.viewCouponDetail': 'View Coupon Details',
// ============ Status Tags ============
'status.active': 'Active',
'status.pending': 'Pending',
'status.expired': 'Expired',
'status.used': 'Used',
'status.processing': 'Processing',
'status.completed': 'Completed',
'status.cancelled': 'Cancelled',
'status.refunding': 'Refunding',
'status.onSale': 'On Sale',
// ============ Empty States ============
'empty.noCoupons': 'No Coupons Yet',
'empty.noCouponsHint': 'Browse the market for great deals',
'empty.browse': 'Browse',
'empty.noTrades': 'No Trades Yet',
'empty.noTradesHint': 'Your trade records will appear here',
'empty.noResults': 'No Results Found',
'empty.noResultsHint': 'Try a different keyword',
'empty.noMessages': 'No Messages',
'empty.noMessagesHint': 'Trade alerts and notifications will appear here',
'empty.networkError': 'Network Error',
'empty.networkErrorHint': 'Please check your connection and try again',
// ============ Coupon Card (shared widget) ============
'couponCard.expiredText': 'Expired',
'couponCard.expiringToday': 'Expires today',
'couponCard.daysToExpiry': 'd left',
'couponCard.expiryFormat': 'Expires',
// ============ Issuer ============
'issuer.title': 'Issuer Portal',
'issuer.verified': 'Verified Issuer',
'issuer.overview': 'Overview',
'issuer.issue': 'Issue',
'issuer.redeem': 'Redeem',
'issuer.finance': 'Finance',
'issuer.more': 'More',
'issuer.totalIssued': 'Total Issued',
'issuer.totalSold': 'Sold',
'issuer.totalRedeemed': 'Redeemed',
'issuer.redeemRate': 'Redeem Rate',
'issuer.quickActions': 'Quick Actions',
'issuer.createCoupon': 'Create Coupon',
'issuer.storeManage': 'Stores',
'issuer.salesAnalysis': 'Analytics',
'issuer.statement': 'Statements',
'issuer.myCoupons': 'My Coupons',
'issuer.listed': 'Listed',
'issuer.underReview': 'Under Review',
'issuer.soldOut': 'Sold Out',
'issuer.unlisted': 'Unlisted',
'issuer.issuedSlash': 'Issued',
'issuer.sold': 'Sold',
'issuer.issueCenter': 'Issue Center',
'issuer.selectTemplate': 'Select Template',
'issuer.voucherType': 'Voucher',
'issuer.discountType': 'Discount',
'issuer.giftCardType': 'Gift Card',
'issuer.storedValueType': 'Stored Value',
'issuer.couponManage': 'Manage',
'issuer.viewAll': 'View All',
'issuer.couponEvents': 'Events',
'issuer.createNew': 'Create New',
'issuer.redeemManage': 'Redemption',
'issuer.redeemTrend': 'Redemption Trends',
'issuer.allStores': 'All Stores',
'issuer.employees': ' staff',
'issuer.financeManage': 'Finance',
'issuer.totalSales': 'Total Sales',
'issuer.settled': 'Settled',
'issuer.pendingSettle': 'Pending',
'issuer.breakage': 'Breakage',
'issuer.withdrawBtn': 'Withdraw',
'issuer.reportBtn': 'Reports',
'issuer.settleDetail': 'Settlement Details',
'issuer.creditLevel': 'Credit Rating',
'issuer.issueQuota': 'Issue Quota',
'issuer.usedQuota': 'Used Quota',
'issuer.dataCenter': 'Data Center',
'issuer.issueSalesRate': 'Issue/Sales/Redeem Rate',
'issuer.userProfile': 'User Insights',
'issuer.userProfileDesc': 'Buyer demographics',
'issuer.creditDetail': 'Credit Details',
'issuer.creditDetailDesc': 'Score and improvement tips',
'issuer.quotaChange': 'Quota Changes',
'issuer.quotaChangeDesc': 'Historical quota adjustments',
'issuer.companyInfo': 'Company Info',
'issuer.companyInfoDesc': 'License / Contacts',
'issuer.settingsItem': 'Settings',
'issuer.settingsItemDesc': 'Notifications / Security',
'issuer.helpItem': 'Help Center',
'issuer.helpItemDesc': 'FAQ & Support',
// ============ Merchant ============
'merchant.today': 'Today',
'merchant.onlineMode': 'Online Mode',
'merchant.offlineMode': 'Offline Mode',
'merchant.pendingSync': 'Pending Sync',
'merchant.syncUnit': ' txns',
'merchant.scanHint': 'Align coupon QR code in the frame',
'merchant.flashlight': 'Flashlight',
'merchant.manualInput': 'Manual Entry',
'merchant.redeemRecords': 'Records',
'merchant.storeData': 'Store Data',
'merchant.inputCode': 'Enter Coupon Code',
'merchant.inputCodeHint': 'Enter code',
'merchant.query': 'Look Up',
'merchant.userNickname': 'User',
'merchant.consumer': 'Consumer',
'merchant.couponName': 'Coupon',
'merchant.faceValue': 'Value',
'merchant.validUntil': 'Valid Until',
'merchant.useCondition': 'Conditions',
'merchant.noMinSpend': 'No minimum spend',
'merchant.confirmRedeem': 'Confirm Redemption',
'merchant.redeemSuccess': 'Redeemed',
'merchant.continueRedeem': 'Continue',
'merchant.synced': 'Synced',
'merchant.pendingSyncLabel': 'Pending',
'merchant.redeemOperator': 'Operator',
'merchant.todayRedeem': 'Today',
'merchant.redeemAmount': 'Amount',
'merchant.weekTrend': 'Weekly Trend',
'merchant.operatorRank': 'Operator Ranking',
// ============ Merchant AI ============
'merchantAi.title': 'AI Smart Assistant',
'merchantAi.redeemAssist': 'Redeem Assist',
'merchantAi.trafficForecast': 'Traffic Forecast',
'merchantAi.anomalyAlert': 'Anomaly Alerts',
'merchantAi.verifyAuth': 'Verify Coupon',
'merchantAi.checkStatus': 'Check Status',
'merchantAi.batchRedeem': 'Batch Redeem',
'merchantAi.feedback': 'Feedback',
'merchantAi.quickActions': 'AI Quick Actions',
'merchantAi.redeemTips': 'Redemption Tips',
'merchantAi.todayHotRedeem': 'Today\'s Top Redemptions',
'merchantAi.countUnit': ' txns',
'merchantAi.aiMarketing': 'AI Marketing Suggestions',
'merchantAi.crossSellTitle': 'Cross-sell Recommendation',
'merchantAi.crossSellDesc': 'Customers buying coffee coupons also show interest in bakery coupons. Consider offering combos.',
'merchantAi.weekendPromoTitle': 'Weekend Promo Suggestion',
'merchantAi.weekendPromoDesc': 'Historical data shows +30% redemptions on Saturdays. Consider launching a weekend flash deal.',
'merchantAi.todayForecast': 'Today\'s Traffic Forecast',
'merchantAi.expectedRedeem': 'Expected Redemptions',
'merchantAi.peakHours': 'Peak Hours',
'merchantAi.expectedRevenue': 'Expected Revenue',
'merchantAi.trafficInsight': '+12% vs. last week. Consider adding 1 more cashier during lunch.',
'merchantAi.hourlyForecast': 'Hourly Forecast',
'merchantAi.weeklyForecast': 'Weekly Forecast',
'merchantAi.staffSuggestion': 'Staff Scheduling',
'merchantAi.pendingCount': 'Pending',
'merchantAi.resolvedToday': 'Resolved Today',
'merchantAi.riskIndex': 'Risk Index',
'merchantAi.riskLow': 'Low',
'merchantAi.activeAlerts': 'Active Alerts',
'merchantAi.highFreqRedeem': 'High-frequency Redemption',
'merchantAi.suspectFakeCode': 'Suspected Fake Code',
'merchantAi.suspiciousPatterns': 'Suspicious Patterns',
'merchantAi.consecutiveRedeem': 'Consecutive Redemptions by Same User',
'merchantAi.offHoursRedeem': 'Off-hours Redemption Attempts',
'merchantAi.expiredRedeemAttempt': 'Expired Coupon Redemption Attempts',
'merchantAi.statusAbnormal': 'Abnormal',
'merchantAi.statusWarning': 'Warning',
'merchantAi.statusNormal': 'Normal',
'merchantAi.expiredBlock': 'Expired Coupon Blocked',
'merchantAi.duplicateBlock': 'Duplicate Redemption Blocked',
'merchantAi.wrongStoreAlert': 'Wrong Store Alert',
'merchantAi.insufficientBalance': 'Insufficient Balance',
'merchantAi.systemRetry': 'System Timeout Retry',
'merchantAi.monday': 'Mon',
'merchantAi.tuesday': 'Tue',
'merchantAi.wednesday': 'Wed',
'merchantAi.thursday': 'Thu',
'merchantAi.friday': 'Fri',
'merchantAi.saturday': 'Sat',
'merchantAi.sunday': 'Sun',
// ============ Pro Mode ============
'proMode.title': 'Pro Mode',
'proMode.toggleDesc': 'View on-chain info and connect external wallets',
'proMode.requireKycL2': 'Requires KYC L2 or above',
'proMode.connected': 'Connected',
'proMode.disconnect': 'Disconnect',
'proMode.connectWallet': 'Connect External Wallet',
'proMode.walletDesc': 'After connecting, you can withdraw platform assets to your own address',
'proMode.showChainAddress': 'Show Chain Address',
'proMode.showChainAddressDesc': 'Display contract address in coupon details',
'proMode.showTxHash': 'Show Tx Hash',
'proMode.showTxHashDesc': 'Display on-chain hash in transaction records',
'proMode.txExplorer': 'Transaction Explorer',
'proMode.txBuyExample': 'Buy Starbucks \$25 Gift Card',
'proMode.txSellExample': 'Sell Amazon \$100 Coupon',
'proMode.confirmed': 'Confirmed',
'proMode.confirming': 'Confirming',
'proMode.viewAllTx': 'View All On-Chain Transactions',
'proMode.chainAssets': 'On-Chain Assets',
'proMode.custodialWallet': 'Custodial Wallet',
'proMode.externalWallet': 'External Wallet (MetaMask)',
'proMode.couponCount5': '5 coupons',
'proMode.couponCount0': '0 coupons',
'proMode.extractToWallet': 'Extract to External Wallet',
'proMode.tradeTrack': 'Trading Track',
'proMode.utilityTrackDesc': 'Coupon validity \u226412 months, no securities license needed',
'proMode.securitiesTrackDesc': 'Long-term investment coupons (coming soon)',
'proMode.mvpNote': 'Current MVP only supports Utility Track',
'proMode.comingSoon': 'Coming Soon',
'proMode.whatIsTitle': 'What is Pro Mode?',
'proMode.whatIsDesc': 'Pro Mode is for users with blockchain experience. After enabling, you can:\n'
'\u2022 Connect external wallets (MetaMask, etc.)\n'
'\u2022 View on-chain addresses and transaction hashes\n'
'\u2022 Extract assets to your own wallet\n'
'\u2022 View underlying on-chain data\n\n'
'KYC L2 verification is required to enable.',
// ============ Receive Coupon ============
'receiveCoupon.title': 'Receive Coupon',
'receiveCoupon.hint': 'Show the QR code or ID below to the sender. They can scan or enter the ID to transfer a coupon to you.',
'receiveCoupon.id': 'Receive ID',
'receiveCoupon.idCopied': 'Receive ID copied to clipboard',
'receiveCoupon.note': 'Received coupons will be automatically added to your holdings.',
};

View File

@ -0,0 +1,686 @@
const Map<String, String> ja = {
// ============ Common ============
'common.confirm': '確認',
'common.cancel': 'キャンセル',
'common.save': '保存',
'common.delete': '削除',
'common.edit': '編集',
'common.search': '検索',
'common.loading': '読み込み中...',
'common.retry': '再試行',
'common.done': '完了',
'common.next': '次へ',
'common.back': '戻る',
'common.close': '閉じる',
'common.more': 'もっと見る',
'common.all': 'すべて',
'common.filter': 'フィルター',
'common.sort': '並び替え',
'common.copy': 'コピー',
'common.copied': 'クリップボードにコピーしました',
'common.today': '今日',
'common.thisWeek': '今週',
'common.thisMonth': '今月',
// ============ Navigation ============
'nav.home': 'ホーム',
'nav.market': 'マーケット',
'nav.myCoupons': 'マイクーポン',
'nav.messages': 'メッセージ',
'nav.profile': 'マイページ',
// ============ Welcome / Auth ============
'welcome.slogan': 'すべてのクーポンに価値を',
'welcome.phoneRegister': '電話番号で登録',
'welcome.emailRegister': 'メールで登録',
'welcome.otherLogin': '他の方法でログイン',
'welcome.hasAccount': 'アカウントをお持ちですか?',
'welcome.login': 'ログイン',
'welcome.agreement': '登録することで「利用規約」と「プライバシーポリシー」に同意したものとみなされます',
'login.title': 'おかえりなさい',
'login.subtitle': 'Genex にログインしてクーポン資産を管理',
'login.passwordTab': 'パスワードでログイン',
'login.codeTab': '認証コードでログイン',
'login.phoneOrEmail': '電話番号またはメール',
'login.password': 'パスワード',
'login.forgotPassword': 'パスワードを忘れた場合',
'login.submit': 'ログイン',
'login.phone': '電話番号',
'login.verifyCode': '認証コード',
'login.getCode': '認証コードを取得',
'register.title': 'アカウント作成',
'register.emailSubtitle': 'メールアドレスで Genex アカウントを登録',
'register.phoneSubtitle': '電話番号で Genex アカウントを登録',
'register.email': 'メールアドレス',
'register.phone': '電話番号',
'register.emailHint': 'メールアドレスを入力',
'register.phoneHint': '電話番号を入力',
'register.code': '認証コード',
'register.codeHint': '6桁の認証コードを入力',
'register.getCode': '認証コードを取得',
'register.setPassword': 'パスワードを設定',
'register.passwordHint': '8〜20文字、英字と数字を含む',
'register.agreement': '以下に同意します',
'register.userAgreement': '「利用規約」',
'register.privacyPolicy': '「プライバシーポリシー」',
'register.submit': '登録',
'register.stepVerify': '認証',
'register.stepPassword': 'パスワード設定',
'register.stepDone': '完了',
'register.rule8chars': '8文字以上',
'register.ruleLetter': '英字を含む',
'register.ruleNumber': '数字を含む',
'forgot.title': 'パスワード再設定',
'forgot.inputAccount': '電話番号またはメールを入力',
'forgot.sendHint': '認証コードをお送りします',
'forgot.accountHint': '電話番号 / メールアドレス',
'forgot.getCode': '認証コードを取得',
'forgot.inputCode': '認証コードを入力',
'forgot.codeSentTo': '認証コードの送信先',
'forgot.codeHint': '6桁の認証コード',
'forgot.resend': '再送信',
'forgot.next': '次へ',
'forgot.setNewPassword': '新しいパスワードを設定',
'forgot.newPasswordHint': '新しいパスワードを入力8文字以上',
'forgot.newPassword': '新しいパスワード',
'forgot.confirmPassword': '新しいパスワードを確認',
'forgot.confirmChange': '変更を確認',
'forgot.success': 'パスワードの変更が完了しました',
'forgot.successHint': '新しいパスワードでログインしてください',
'forgot.backToLogin': 'ログインに戻る',
// ============ Home ============
'home.searchHint': 'クーポン、ブランド、カテゴリを検索...',
'home.dining': 'グルメ',
'home.shopping': 'ショッピング',
'home.entertainment': 'エンタメ',
'home.travel': 'トラベル',
'home.lifestyle': 'ライフスタイル',
'home.brand': 'ブランド',
'home.discount': 'セール',
'home.allCategories': 'すべて',
'home.featuredCoupons': '厳選クーポン',
'home.viewAllCoupons': 'すべて見る',
'home.aiRecommend': 'AI おすすめ',
'home.aiRecommendDesc': 'あなたの好みに基づき、コスパの高いクーポンを3枚発見しました',
'home.bannerNewUser': '新規ユーザー特典',
'home.bannerNewUserDesc': '初回注文 \$10 割引',
'home.bannerDiscount': 'タイムセール',
'home.bannerDiscountDesc': '全品最大30%オフ',
'home.bannerHot': '人気おすすめ',
'home.bannerHotDesc': '厳選割引クーポン',
// ============ Market ============
'market.title': 'マーケットプレイス',
'market.primary': 'プライマリー市場(新品)',
'market.secondary': 'セカンダリー市場(リセール)',
'market.dining': 'グルメ',
'market.shopping': 'ショッピング',
'market.entertainment': 'エンタメ',
'market.travel': 'トラベル',
'market.lifestyle': 'ライフスタイル',
'market.sports': 'スポーツ',
'market.discountRate': '割引率',
'market.priceUp': '価格↑',
'market.priceDown': '価格↓',
'market.expiryDate': '有効期限',
'market.issuePrice': '発行価格',
'market.faceValue': '額面',
'market.discount': '割引',
'market.totalSupply': '発行数量',
'market.salesProgress': '販売進捗',
'market.upcoming': '開始予定',
'market.subscribing': '申込受付中',
'market.ended': '終了',
'market.timeToStart': '開始まで',
'market.couponBrand': 'クーポン名/ブランド',
'market.latestPrice': '最新価格',
'market.change24h': '24h騰落',
'market.discountSuffix': '',
// ============ My Coupons ============
'myCoupons.title': 'マイクーポン',
'myCoupons.usable': '利用可能',
'myCoupons.pendingRedeem': '利用待ち',
'myCoupons.expired': '期限切れ',
'myCoupons.faceValue': '額面',
'myCoupons.transfer': '譲渡',
'myCoupons.sell': '売却',
'myCoupons.expiredText': '期限切れ',
'myCoupons.expiringToday': '本日期限',
'myCoupons.daysToExpiry': '日後に期限切れ',
// ============ Coupon Detail ============
'couponDetail.title': 'クーポン詳細',
'couponDetail.favorite': 'お気に入り',
'couponDetail.buyNow': '今すぐ購入',
'couponDetail.saveBadge': '額面よりお得',
'couponDetail.faceValue': '額面',
'couponDetail.validUntil': '有効期限',
'couponDetail.type': 'タイプ',
'couponDetail.issuer': '発行元',
'couponDetail.consumeCoupon': '消費クーポン',
'couponDetail.usageNote': '利用案内',
'couponDetail.allStores': '全国の店舗で利用可能',
'couponDetail.canTransfer': '友達に譲渡可能',
'couponDetail.useAnytime': '有効期間内いつでも利用可能',
'couponDetail.noStack': '併用不可',
'couponDetail.noCash': '現金との交換不可',
'couponDetail.stores': '利用可能店舗',
'couponDetail.storeCount': '全国 12,800+ 店舗',
'couponDetail.storeDesc': '全国のすべての直営店舗で利用可能',
'couponDetail.priceTrend': '価格推移',
'couponDetail.last30Days': '過去30日間',
'couponDetail.highest': '最高値',
'couponDetail.lowest': '最安値',
'couponDetail.average': '平均価格',
'couponDetail.tradeHistory': '取引履歴',
'couponDetail.nearbyStores': '近くの利用可能店舗',
'couponDetail.distance': '距離',
'couponDetail.open': '営業中',
'couponDetail.similar': '類似クーポン',
// ============ My Coupon Detail ============
'myCoupon.title': 'クーポン詳細',
'myCoupon.active': '利用可能',
'myCoupon.showQrHint': 'このQRコードを店舗スタッフに提示してスキャンしてもらってください',
'myCoupon.switchBarcode': 'バーコードに切替',
'myCoupon.faceValue': '額面',
'myCoupon.purchasePrice': '購入価格',
'myCoupon.validUntil': '有効期限',
'myCoupon.orderNo': '注文番号',
'myCoupon.resellCount': '残り転売回数',
'myCoupon.transfer': '譲渡',
'myCoupon.sell': '売却',
'myCoupon.usageNote': '利用案内',
'myCoupon.useInStore': '全国の店舗で利用可能',
'myCoupon.useInTime': '有効期限内にご利用ください',
'myCoupon.onePerVisit': '1回のお会計につき1枚のみ利用可能',
'myCoupon.noCash': '現金との交換不可',
'myCoupon.extractToWallet': '外部ウォレットに引き出す',
'myCoupon.requireKycL2': 'KYC L2以上の認証が必要',
'myCoupon.viewTrades': '取引履歴を見る',
'myCoupon.help': '利用ヘルプ',
// ============ Order Confirm ============
'orderConfirm.title': '注文確認',
'orderConfirm.quantity': '購入数量',
'orderConfirm.paymentMethod': '支払い方法',
'orderConfirm.bankCard': '銀行カード/クレジットカード',
'orderConfirm.priceDetail': '価格明細',
'orderConfirm.buyingNote': '消費用クーポンを購入します',
'orderConfirm.total': '合計',
'orderConfirm.confirmPay': '支払いを確認',
'orderConfirm.unitPrice': '単価',
'orderConfirm.count': '数量',
'orderConfirm.saveBadge': '額面よりお得',
'orderConfirm.biometricHint': '支払いを完了するには指紋または顔認証を行ってください',
'orderConfirm.usePasswordPay': 'パスワードで支払う',
// ============ Payment ============
'payment.title': '支払い方法を選択',
'payment.addNew': '新しい支払い方法を追加',
'payment.confirmPay': '支払いを確認',
'payment.bankTransfer': '銀行振込',
'paymentSuccess.title': '支払い完了',
'paymentSuccess.hint': 'クーポンが届きました。「マイクーポン」で確認できます',
'paymentSuccess.couponName': 'クーポン名',
'paymentSuccess.payAmount': '支払い金額',
'paymentSuccess.orderNo': '注文番号',
'paymentSuccess.payTime': '支払い日時',
'paymentSuccess.viewMyCoupon': 'マイクーポンを見る',
'paymentSuccess.continueBrowse': '買い物を続ける',
// ============ Search ============
'search.hint': 'クーポン、ブランド、カテゴリを検索...',
'search.cancel': 'キャンセル',
'search.hotSearch': '人気の検索',
'search.history': '検索履歴',
'search.clear': 'クリア',
'search.diningCoupon': 'グルメクーポン',
'search.discountCoupon': '割引クーポン',
'search.travel': '旅行',
// ============ Redeem ============
'redeem.title': 'クーポンコードを提示',
'redeem.faceValue': '額面',
'redeem.validTime': '有効期間',
'redeem.refresh': 'コードを更新',
'redeem.showHint': 'このコードを店舗スタッフに提示してスキャンしてもらってください。画面の明るさは自動的に最大に調整されました',
// ============ Sell Order ============
'sellOrder.title': '売り注文を出す',
'sellOrder.faceValue': '額面',
'sellOrder.credit': '信用',
'sellOrder.setPrice': '販売価格を設定',
'sellOrder.price': '販売価格',
'sellOrder.aiSuggest': 'AI推奨価格',
'sellOrder.bestDealRate': 'この価格が最も成約率が高いです',
'sellOrder.discountRate': '割引率',
'sellOrder.platformFee': 'プラットフォーム手数料 (1.5%)',
'sellOrder.estimatedReceive': '受取見込額',
'sellOrder.marketAvg': '現在の市場平均価格',
'sellOrder.recent24hTrades': '直近24時間の取引',
'sellOrder.tradesUnit': '',
'sellOrder.confirmList': '出品を確認',
'sellOrder.success': '出品完了',
'sellOrder.successHint': 'クーポンが市場に出品されました。買い手が注文すると自動的に成約します。',
'sellOrder.ok': 'OK',
// ============ Trading Page ============
'tradingPage.title': 'マイ取引',
'tradingPage.pendingOrders': '出品中の注文',
'tradingPage.tradeRecords': '取引履歴',
'tradingPage.listPrice': '出品価格',
'tradingPage.listTime': '出品日時',
'tradingPage.cancelOrder': '注文取消',
'tradingPage.buy': '購入',
'tradingPage.sell': '売却',
// ============ Transfer ============
'transfer.title': '友達に譲渡',
'transfer.searchFriend': '友達を検索(電話番号/ユーザー名)',
'transfer.confirmTransfer': '譲渡を確認',
'transfer.success': '譲渡完了',
'transfer.confirm': 'OK',
'transfer.cancel': 'キャンセル',
// ============ Wallet ============
'wallet.myBalance': '残高',
'wallet.totalBalance': '合計残高',
'wallet.withdrawable': '出金可能',
'wallet.frozen': '凍結中',
'wallet.deposit': '入金',
'wallet.withdraw': '出金',
'wallet.records': '取引履歴',
'wallet.filter': 'フィルター',
'wallet.buyIn': '購入',
'wallet.sellOut': '売却',
'wallet.giftTransfer': '譲渡',
'wallet.redeemUse': '利用',
'deposit.title': '入金',
'deposit.currentBalance': '現在の残高',
'deposit.amount': '入金額',
'deposit.custom': 'カスタム金額',
'deposit.paymentMethod': '支払い方法',
'deposit.submit': '入金',
'withdraw.title': '出金',
'withdraw.availableBalance': '出金可能残高',
'withdraw.amount': '出金額',
'withdraw.all': '全額',
'withdraw.to': '出金先',
'withdraw.savingsAccount': '普通預金口座',
'withdraw.fee': '手数料 (0.5%)',
'withdraw.actualReceive': '実際の受取額',
'withdraw.estimateTime': '1〜2営業日で入金予定',
'withdraw.submit': '出金を確認',
'txRecords.title': '取引履歴',
'txRecords.all': 'すべて',
'txRecords.buy': '購入',
'txRecords.sell': '売却',
'txRecords.transfer': '譲渡',
'txRecords.noRecords': '記録がありません',
'txRecords.orderNo': '注文番号',
'txRecords.transferTo': '譲渡先',
// ============ Profile ============
'profile.favorites': 'お気に入り',
'profile.orders': '注文',
'profile.coupons': 'クーポン',
'profile.wallet': 'ウォレット',
'profile.account': 'アカウント',
'profile.trade': '取引',
'profile.settings': '設定',
'profile.holdCoupons': '保有',
'profile.saved': '節約',
'profile.credit': '信用',
'profile.creditScore': '信用スコア',
'profile.myTrades': 'マイ取引',
'profile.walletBalance': 'ウォレット残高',
'profile.paymentManage': '支払い管理',
'profile.kyc': '本人確認',
'profile.proMode': 'プロモード',
'profile.myFavorites': 'お気に入り',
'profile.securitySettings': 'セキュリティ設定',
'profile.advancedSettings': '詳細設定',
'profile.aboutGenex': 'Genex について',
'profile.helpCenter': 'ヘルプセンター',
'profile.issuerPortal': '発行者ポータル',
'profile.merchantPortal': '加盟店ポータル',
'profile.simplifiedChinese': '簡体中国語',
'profile.logout': 'ログアウト',
// ============ Settings ============
'settings.title': '設定',
'settings.accountSecurity': 'アカウントとセキュリティ',
'settings.phone': '電話番号',
'settings.email': 'メール',
'settings.changePassword': 'パスワード変更',
'settings.identity': '本人確認',
'settings.paymentManage': '支払い管理',
'settings.paymentMethod': '支払い方法',
'settings.bankAccount': '銀行口座',
'settings.paymentPassword': '支払いパスワード',
'settings.notifications': '通知設定',
'settings.tradeNotify': '取引通知',
'settings.expiryRemind': '期限切れリマインダー',
'settings.marketChange': '相場変動',
'settings.marketingPush': 'プロモーション通知',
'settings.general': '一般',
'settings.language': '言語',
'settings.currency': '通貨',
'settings.clearCache': 'キャッシュをクリア',
'settings.about': 'アプリについて',
'settings.version': 'バージョン',
'settings.userAgreement': '利用規約',
'settings.privacyPolicy': 'プライバシーポリシー',
'settings.helpCenter': 'ヘルプセンター',
'settings.logout': 'ログアウト',
'settings.selectCurrency': '表示通貨を選択',
'settings.currencyNote': 'この設定は取引ページの価格表示通貨に影響します',
'settings.selectLanguage': '言語を選択',
'settings.currencySymbol': '記号',
// ============ KYC ============
'kyc.title': '本人確認',
'kyc.currentLevel': '現在の認証レベル',
'kyc.l1Title': 'L1 基本認証',
'kyc.l1Desc': '電話番号 + メール認証',
'kyc.l1Limit': '1日の購入上限 \$500',
'kyc.l1Feature': 'クーポンの購入・利用が可能',
'kyc.l2Title': 'L2 本人認証',
'kyc.l2Desc': '身分証/パスポート認証',
'kyc.l2Limit': '1日の購入上限 \$5,000',
'kyc.l2Feature': 'セカンダリー市場取引・P2P譲渡を解放',
'kyc.l3Title': 'L3 上級認証',
'kyc.l3Desc': 'ビデオ面談 + 住所証明',
'kyc.l3Limit': '上限なし',
'kyc.l3Feature': '大口取引・出金制限なし',
'kyc.completed': '完了',
'kyc.goVerify': '認証する',
'kyc.badgeLabel': '認証済',
// ============ Payment Management ============
'payManage.title': '支払い管理',
'payManage.myCards': '登録カード',
'payManage.addCard': '新しいカードを追加',
'payManage.bankAccount': '銀行口座(出金用)',
'payManage.paymentSecurity': '支払いセキュリティ',
'payManage.paymentPassword': '支払いパスワード',
'payManage.passwordSet': '設定済み',
'payManage.biometricPay': '指紋/顔認証支払い',
'payManage.biometricEnabled': '有効',
'payManage.noPasswordPay': 'パスワード不要の支払い',
'payManage.noPasswordLimit': '1回あたり\$10以下',
// ============ AI Chat ============
'aiChat.title': 'AI アシスタント',
'aiChat.greeting': 'こんにちはGenex AI アシスタントです。お得なクーポンの発見、価格比較、おすすめの組み合わせをお手伝いします。こんな質問をどうぞ:',
'aiChat.suggest1': 'おすすめのクーポンを教えて',
'aiChat.suggest2': 'Starbucksのクーポンは買い得',
'aiChat.suggest3': '価格比較をして',
'aiChat.suggest4': 'クーポンがもうすぐ期限切れ、どうすればいい?',
'aiChat.inputHint': 'クーポンについて何でも聞いてください...',
'aiChat.confirmAction': '実行を確認',
'aiChat.riskLow': '低リスク',
'aiChat.riskNormal': '要確認',
'aiChat.riskHigh': '高リスク',
// ============ AI Fab ============
'aiFab.title': 'AI アシスタント',
'aiFab.greeting': 'こんにちはGenex AI アシスタントです。クーポン資産の管理、お得情報の検索、価格分析をお手伝いします。何かお手伝いできることはありますか?',
'aiFab.inputHint': 'メッセージを入力...',
'aiFab.suggest1': '割引率の高いクーポンを探して',
'aiFab.suggest2': '期限切れ間近のクーポンはある?',
'aiFab.suggest3': '今日のおすすめクーポンは?',
'aiFab.suggest4': 'クーポン資産を分析して',
// ============ Messages ============
'message.title': 'メッセージ',
'message.markAllRead': 'すべて既読',
'message.tabTrade': '取引',
'message.tabExpiry': '期限',
'message.tabAnnouncement': 'お知らせ',
'message.detailTitle': 'メッセージ詳細',
'message.tradeNotify': '取引通知',
'message.expiryRemind': '期限切れリマインダー',
'message.systemNotify': 'システム通知',
'message.promoNotify': 'キャンペーン情報',
'message.tradeSuccess': '取引完了通知',
'message.couponName': 'クーポン名',
'message.faceValue': '額面',
'message.payAmount': '支払い金額',
'message.orderNo': '注文番号',
'message.payMethod': '支払い方法',
'message.viewCouponDetail': 'クーポン詳細を見る',
// ============ Status Tags ============
'status.active': '利用可能',
'status.pending': '利用待ち',
'status.expired': '期限切れ',
'status.used': '使用済み',
'status.processing': '処理中',
'status.completed': '完了',
'status.cancelled': 'キャンセル済み',
'status.refunding': '返金処理中',
'status.onSale': '出品中',
// ============ Empty States ============
'empty.noCoupons': 'クーポンがありません',
'empty.noCouponsHint': 'マーケットでお得なクーポンを探してみましょう',
'empty.browse': '見てみる',
'empty.noTrades': '取引履歴がありません',
'empty.noTradesHint': '最初の取引を完了するとここに表示されます',
'empty.noResults': '結果が見つかりません',
'empty.noResultsHint': '別のキーワードで試してみてください',
'empty.noMessages': 'メッセージはありません',
'empty.noMessagesHint': '取引通知やシステムのお知らせがここに表示されます',
'empty.networkError': 'ネットワーク接続に失敗しました',
'empty.networkErrorHint': 'ネットワーク設定を確認してから再試行してください',
// ============ Coupon Card (shared widget) ============
'couponCard.expiredText': '期限切れ',
'couponCard.expiringToday': '本日期限',
'couponCard.daysToExpiry': '日後に期限切れ',
'couponCard.expiryFormat': '期限',
// ============ Issuer ============
'issuer.title': '発行者管理',
'issuer.verified': '認証済み発行者',
'issuer.overview': '概要',
'issuer.issue': '発行',
'issuer.redeem': '利用処理',
'issuer.finance': '財務',
'issuer.more': 'もっと見る',
'issuer.totalIssued': '総発行数',
'issuer.totalSold': '販売済み',
'issuer.totalRedeemed': '利用処理済み',
'issuer.redeemRate': '利用率',
'issuer.quickActions': 'クイック操作',
'issuer.createCoupon': 'クーポン作成',
'issuer.storeManage': '店舗管理',
'issuer.salesAnalysis': '売上分析',
'issuer.statement': '精算書',
'issuer.myCoupons': 'マイクーポン',
'issuer.listed': '掲載中',
'issuer.underReview': '審査中',
'issuer.soldOut': '完売',
'issuer.unlisted': '非掲載',
'issuer.issuedSlash': '発行',
'issuer.sold': '販売済み',
'issuer.issueCenter': '発行センター',
'issuer.selectTemplate': 'クーポンテンプレートを選択',
'issuer.voucherType': '割引券',
'issuer.discountType': '値引きクーポン',
'issuer.giftCardType': 'ギフトカード',
'issuer.storedValueType': 'プリペイドクーポン',
'issuer.couponManage': 'クーポン管理',
'issuer.viewAll': 'すべて見る',
'issuer.couponEvents': 'クーポンイベント',
'issuer.createNew': '新規クーポン作成',
'issuer.redeemManage': '利用処理管理',
'issuer.redeemTrend': '利用処理トレンド',
'issuer.allStores': '全店舗',
'issuer.employees': '名のスタッフ',
'issuer.financeManage': '財務管理',
'issuer.totalSales': '総売上額',
'issuer.settled': '入金済み',
'issuer.pendingSettle': '精算待ち',
'issuer.breakage': 'Breakage',
'issuer.withdrawBtn': '出金',
'issuer.reportBtn': '精算レポート',
'issuer.settleDetail': '精算明細',
'issuer.creditLevel': '信用等級',
'issuer.issueQuota': '発行枠',
'issuer.usedQuota': '使用済み枠',
'issuer.dataCenter': 'データセンター',
'issuer.issueSalesRate': '発行数/販売数/利用率',
'issuer.userProfile': 'ユーザープロフィール',
'issuer.userProfileDesc': '購入ユーザーの分布分析',
'issuer.creditDetail': '信用詳細',
'issuer.creditDetailDesc': 'スコア詳細と改善提案',
'issuer.quotaChange': '枠の変動',
'issuer.quotaChangeDesc': '過去の枠調整履歴',
'issuer.companyInfo': '企業情報',
'issuer.companyInfoDesc': '営業許可証/担当者',
'issuer.settingsItem': '設定',
'issuer.settingsItemDesc': '通知/セキュリティ/言語',
'issuer.helpItem': 'ヘルプセンター',
'issuer.helpItemDesc': 'よくある質問とサポート',
// ============ Merchant ============
'merchant.today': '今日',
'merchant.onlineMode': 'オンラインモード',
'merchant.offlineMode': 'オフラインモード',
'merchant.pendingSync': '同期待ち',
'merchant.syncUnit': '',
'merchant.scanHint': 'クーポンのQRコードをスキャン枠に合わせてください',
'merchant.flashlight': 'ライト',
'merchant.manualInput': '手動入力',
'merchant.redeemRecords': '利用処理履歴',
'merchant.storeData': '店舗データ',
'merchant.inputCode': 'クーポンコードを手動入力',
'merchant.inputCodeHint': 'クーポンコードを入力',
'merchant.query': '照会',
'merchant.userNickname': 'ユーザー名',
'merchant.consumer': '消費者',
'merchant.couponName': 'クーポン名',
'merchant.faceValue': '額面',
'merchant.validUntil': '有効期限',
'merchant.useCondition': '利用条件',
'merchant.noMinSpend': '最低利用金額なし',
'merchant.confirmRedeem': '利用処理を確認',
'merchant.redeemSuccess': '利用処理完了',
'merchant.continueRedeem': '続けて処理',
'merchant.synced': '同期済み',
'merchant.pendingSyncLabel': '同期待ち',
'merchant.redeemOperator': '処理担当者',
'merchant.todayRedeem': '本日の利用処理',
'merchant.redeemAmount': '利用処理金額',
'merchant.weekTrend': '今週のトレンド',
'merchant.operatorRank': '担当者ランキング',
// ============ Merchant AI ============
'merchantAi.title': 'AI スマートアシスタント',
'merchantAi.redeemAssist': '利用処理アシスト',
'merchantAi.trafficForecast': '客数予測',
'merchantAi.anomalyAlert': '異常アラート',
'merchantAi.verifyAuth': 'クーポン検証',
'merchantAi.checkStatus': 'ステータス確認',
'merchantAi.batchRedeem': '一括処理',
'merchantAi.feedback': 'フィードバック',
'merchantAi.quickActions': 'AI クイック操作',
'merchantAi.redeemTips': '利用処理のヒント',
'merchantAi.todayHotRedeem': '本日の人気利用処理',
'merchantAi.countUnit': '',
'merchantAi.aiMarketing': 'AI マーケティング提案',
'merchantAi.crossSellTitle': 'クロスセル提案',
'merchantAi.crossSellDesc': 'コーヒークーポン購入者はベーカリークーポンにも関心があります。セット販売をお勧めします',
'merchantAi.weekendPromoTitle': '週末プロモーション提案',
'merchantAi.weekendPromoDesc': '過去のデータでは土曜日の利用処理が+30%。週末限定セールの実施をお勧めします',
'merchantAi.todayForecast': '本日の客数予測',
'merchantAi.expectedRedeem': '予想利用処理数',
'merchantAi.peakHours': 'ピーク時間帯',
'merchantAi.expectedRevenue': '予想収益',
'merchantAi.trafficInsight': '先週同期比+12%。昼のピーク時にレジ担当を1名追加することをお勧めします',
'merchantAi.hourlyForecast': '時間帯別予測',
'merchantAi.weeklyForecast': '今週の予測',
'merchantAi.staffSuggestion': 'シフト提案',
'merchantAi.pendingCount': '未処理',
'merchantAi.resolvedToday': '本日処理済み',
'merchantAi.riskIndex': 'リスク指数',
'merchantAi.riskLow': '',
'merchantAi.activeAlerts': 'アクティブアラート',
'merchantAi.highFreqRedeem': '高頻度利用処理検知',
'merchantAi.suspectFakeCode': '偽造コード疑い',
'merchantAi.suspiciousPatterns': '不審なパターン検知',
'merchantAi.consecutiveRedeem': '同一ユーザーの連続利用処理',
'merchantAi.offHoursRedeem': '営業時間外の利用処理試行',
'merchantAi.expiredRedeemAttempt': '期限切れクーポンの利用処理試行',
'merchantAi.statusAbnormal': '異常',
'merchantAi.statusWarning': '注意',
'merchantAi.statusNormal': '正常',
'merchantAi.expiredBlock': '期限切れクーポンブロック',
'merchantAi.duplicateBlock': '重複利用処理ブロック',
'merchantAi.wrongStoreAlert': '対象外店舗アラート',
'merchantAi.insufficientBalance': '残高不足',
'merchantAi.systemRetry': 'システムタイムアウト再試行',
'merchantAi.monday': '',
'merchantAi.tuesday': '',
'merchantAi.wednesday': '',
'merchantAi.thursday': '',
'merchantAi.friday': '',
'merchantAi.saturday': '',
'merchantAi.sunday': '',
// ============ Pro Mode ============
'proMode.title': 'プロモード',
'proMode.toggleDesc': 'オンチェーン情報の閲覧と外部ウォレット接続が可能',
'proMode.requireKycL2': 'KYC L2以上の認証が必要',
'proMode.connected': '接続済み',
'proMode.disconnect': '切断',
'proMode.connectWallet': '外部ウォレットを接続',
'proMode.walletDesc': '外部ウォレット接続後、プラットフォーム資産を自分のアドレスに引き出せます',
'proMode.showChainAddress': 'チェーンアドレスを表示',
'proMode.showChainAddressDesc': 'クーポン詳細にコントラクトアドレスを表示',
'proMode.showTxHash': '取引ハッシュを表示',
'proMode.showTxHashDesc': '取引記録にオンチェーンハッシュを表示',
'proMode.txExplorer': 'トランザクションエクスプローラー',
'proMode.txBuyExample': 'スターバックス \$25 ギフトカード購入',
'proMode.txSellExample': 'Amazon \$100 クーポン売却',
'proMode.confirmed': '確認済み',
'proMode.confirming': '確認中',
'proMode.viewAllTx': 'すべてのオンチェーン取引を表示',
'proMode.chainAssets': 'オンチェーン資産',
'proMode.custodialWallet': 'プラットフォーム管理ウォレット',
'proMode.externalWallet': '外部ウォレット (MetaMask)',
'proMode.couponCount5': '5 枚のクーポン',
'proMode.couponCount0': '0 枚のクーポン',
'proMode.extractToWallet': '外部ウォレットに引き出し',
'proMode.tradeTrack': '取引トラック',
'proMode.utilityTrackDesc': 'クーポン有効期限\u226412ヶ月、証券ライセンス不要',
'proMode.securitiesTrackDesc': '長期投資型クーポン商品(近日公開)',
'proMode.mvpNote': '現在のMVP版はUtility Trackのみ対応',
'proMode.comingSoon': '近日公開',
'proMode.whatIsTitle': 'プロモードとは?',
'proMode.whatIsDesc': 'プロモードはブロックチェーン経験のあるユーザー向けです。有効化後:\n'
'\u2022 外部ウォレット接続MetaMask等\n'
'\u2022 オンチェーンアドレスと取引ハッシュの閲覧\n'
'\u2022 資産を自分のウォレットに引き出し\n'
'\u2022 基盤となるオンチェーンデータの閲覧\n\n'
'KYC L2認証完了後に有効化できます。',
// ============ Receive Coupon ============
'receiveCoupon.title': 'クーポンを受取',
'receiveCoupon.hint': '下のQRコードまたは受取IDを相手に見せてください。相手がスキャンまたはIDを入力することで、クーポンがあなたのウォレットに届きます。',
'receiveCoupon.id': '受取ID',
'receiveCoupon.idCopied': '受取IDをクリップボードにコピーしました',
'receiveCoupon.note': '受取ったクーポンは自動的にウォレットに保存されます。ホーム画面のウォレットから確認・管理できます。',
};

View File

@ -0,0 +1,686 @@
const Map<String, String> zhCN = {
// ============ Common ============
'common.confirm': '确认',
'common.cancel': '取消',
'common.save': '保存',
'common.delete': '删除',
'common.edit': '编辑',
'common.search': '搜索',
'common.loading': '加载中...',
'common.retry': '重试',
'common.done': '完成',
'common.next': '下一步',
'common.back': '返回',
'common.close': '关闭',
'common.more': '更多',
'common.all': '全部',
'common.filter': '筛选',
'common.sort': '排序',
'common.copy': '复制',
'common.copied': '已复制到剪贴板',
'common.today': '今日',
'common.thisWeek': '本周',
'common.thisMonth': '本月',
// ============ Navigation ============
'nav.home': '首页',
'nav.market': '市场',
'nav.myCoupons': '我的券',
'nav.messages': '消息',
'nav.profile': '我的',
// ============ Welcome / Auth ============
'welcome.slogan': '让每一张券都有价值',
'welcome.phoneRegister': '手机号注册',
'welcome.emailRegister': '邮箱注册',
'welcome.otherLogin': '其他方式登录',
'welcome.hasAccount': '已有账号?',
'welcome.login': '登录',
'welcome.agreement': '注册即表示同意《用户协议》和《隐私政策》',
'login.title': '欢迎回来',
'login.subtitle': '登录 Genex 管理你的券资产',
'login.passwordTab': '密码登录',
'login.codeTab': '验证码登录',
'login.phoneOrEmail': '手机号或邮箱',
'login.password': '密码',
'login.forgotPassword': '忘记密码?',
'login.submit': '登录',
'login.phone': '手机号',
'login.verifyCode': '验证码',
'login.getCode': '获取验证码',
'register.title': '创建账号',
'register.emailSubtitle': '使用邮箱注册 Genex 账号',
'register.phoneSubtitle': '使用手机号注册 Genex 账号',
'register.email': '邮箱地址',
'register.phone': '手机号',
'register.emailHint': '请输入邮箱地址',
'register.phoneHint': '请输入手机号',
'register.code': '验证码',
'register.codeHint': '请输入6位验证码',
'register.getCode': '获取验证码',
'register.setPassword': '设置密码',
'register.passwordHint': '8-20位含字母和数字',
'register.agreement': '我已阅读并同意',
'register.userAgreement': '《用户协议》',
'register.privacyPolicy': '《隐私政策》',
'register.submit': '注册',
'register.stepVerify': '验证',
'register.stepPassword': '设密码',
'register.stepDone': '完成',
'register.rule8chars': '8位以上',
'register.ruleLetter': '含字母',
'register.ruleNumber': '含数字',
'forgot.title': '找回密码',
'forgot.inputAccount': '输入手机号或邮箱',
'forgot.sendHint': '我们将向您发送验证码',
'forgot.accountHint': '手机号 / 邮箱地址',
'forgot.getCode': '获取验证码',
'forgot.inputCode': '输入验证码',
'forgot.codeSentTo': '验证码已发送至',
'forgot.codeHint': '6位验证码',
'forgot.resend': '重新发送',
'forgot.next': '下一步',
'forgot.setNewPassword': '设置新密码',
'forgot.newPasswordHint': '请输入新密码8位以上',
'forgot.newPassword': '新密码',
'forgot.confirmPassword': '确认新密码',
'forgot.confirmChange': '确认修改',
'forgot.success': '密码修改成功',
'forgot.successHint': '请使用新密码登录',
'forgot.backToLogin': '返回登录',
// ============ Home ============
'home.searchHint': '搜索券、品牌、分类...',
'home.dining': '餐饮',
'home.shopping': '购物',
'home.entertainment': '娱乐',
'home.travel': '出行',
'home.lifestyle': '生活',
'home.brand': '品牌',
'home.discount': '折扣',
'home.allCategories': '全部',
'home.featuredCoupons': '精选好券',
'home.viewAllCoupons': '查看全部',
'home.aiRecommend': 'AI 推荐',
'home.aiRecommendDesc': '根据你的偏好发现了3张高性价比券',
'home.bannerNewUser': '新用户专享',
'home.bannerNewUserDesc': '首单立减 \$10',
'home.bannerDiscount': '限时折扣',
'home.bannerDiscountDesc': '全场低至7折',
'home.bannerHot': '热门推荐',
'home.bannerHotDesc': '精选高折扣券',
// ============ Market ============
'market.title': '交易市场',
'market.primary': '一级市场(全新)',
'market.secondary': '二级市场(转售)',
'market.dining': '餐饮',
'market.shopping': '购物',
'market.entertainment': '娱乐',
'market.travel': '出行',
'market.lifestyle': '生活',
'market.sports': '运动',
'market.discountRate': '折扣率',
'market.priceUp': '价格↑',
'market.priceDown': '价格↓',
'market.expiryDate': '到期时间',
'market.issuePrice': '发行价',
'market.faceValue': '面值',
'market.discount': '折扣',
'market.totalSupply': '发行量',
'market.salesProgress': '销售进度',
'market.upcoming': '即将开始',
'market.subscribing': '申购中',
'market.ended': '已结束',
'market.timeToStart': '距开始',
'market.couponBrand': '券名/品牌',
'market.latestPrice': '最新价',
'market.change24h': '24h涨跌',
'market.discountSuffix': '',
// ============ My Coupons ============
'myCoupons.title': '我的券',
'myCoupons.usable': '可使用',
'myCoupons.pendingRedeem': '待核销',
'myCoupons.expired': '已过期',
'myCoupons.faceValue': '面值',
'myCoupons.transfer': '转赠',
'myCoupons.sell': '出售',
'myCoupons.expiredText': '已过期',
'myCoupons.expiringToday': '今天到期',
'myCoupons.daysToExpiry': '天后到期',
// ============ Coupon Detail ============
'couponDetail.title': '券详情',
'couponDetail.favorite': '收藏',
'couponDetail.buyNow': '立即购买',
'couponDetail.saveBadge': '比面值节省',
'couponDetail.faceValue': '面值',
'couponDetail.validUntil': '有效期',
'couponDetail.type': '类型',
'couponDetail.issuer': '发行方',
'couponDetail.consumeCoupon': '消费券',
'couponDetail.usageNote': '使用说明',
'couponDetail.allStores': '全国门店通用',
'couponDetail.canTransfer': '可转赠给好友',
'couponDetail.useAnytime': '有效期内随时使用',
'couponDetail.noStack': '不可叠加使用',
'couponDetail.noCash': '不可兑换现金',
'couponDetail.stores': '使用门店',
'couponDetail.storeCount': '全国 12,800+ 门店',
'couponDetail.storeDesc': '支持全国所有直营门店使用',
'couponDetail.priceTrend': '价格走势',
'couponDetail.last30Days': '近30天',
'couponDetail.highest': '最高',
'couponDetail.lowest': '最低',
'couponDetail.average': '均价',
'couponDetail.tradeHistory': '历史成交',
'couponDetail.nearbyStores': '附近可用门店',
'couponDetail.distance': '距离',
'couponDetail.open': '营业中',
'couponDetail.similar': '同类券推荐',
// ============ My Coupon Detail ============
'myCoupon.title': '券详情',
'myCoupon.active': '可使用',
'myCoupon.showQrHint': '出示此二维码给商户扫描核销',
'myCoupon.switchBarcode': '切换条形码',
'myCoupon.faceValue': '面值',
'myCoupon.purchasePrice': '购买价格',
'myCoupon.validUntil': '有效期',
'myCoupon.orderNo': '订单号',
'myCoupon.resellCount': '剩余可转售次数',
'myCoupon.transfer': '转赠',
'myCoupon.sell': '出售',
'myCoupon.usageNote': '使用说明',
'myCoupon.useInStore': '全国门店通用',
'myCoupon.useInTime': '请在有效期内使用',
'myCoupon.onePerVisit': '每次消费仅可使用一张',
'myCoupon.noCash': '不可兑换现金',
'myCoupon.extractToWallet': '提取到外部钱包',
'myCoupon.requireKycL2': '需KYC L2+认证',
'myCoupon.viewTrades': '查看交易记录',
'myCoupon.help': '使用帮助',
// ============ Order Confirm ============
'orderConfirm.title': '确认订单',
'orderConfirm.quantity': '购买数量',
'orderConfirm.paymentMethod': '支付方式',
'orderConfirm.bankCard': '银行卡/信用卡',
'orderConfirm.priceDetail': '价格明细',
'orderConfirm.buyingNote': '您正在购买消费券用于消费',
'orderConfirm.total': '合计',
'orderConfirm.confirmPay': '确认支付',
'orderConfirm.unitPrice': '单价',
'orderConfirm.count': '数量',
'orderConfirm.saveBadge': '比面值节省',
'orderConfirm.biometricHint': '请验证指纹或面容以完成支付',
'orderConfirm.usePasswordPay': '使用密码支付',
// ============ Payment ============
'payment.title': '选择支付方式',
'payment.addNew': '添加新支付方式',
'payment.confirmPay': '确认支付',
'payment.bankTransfer': '银行转账',
'paymentSuccess.title': '支付成功',
'paymentSuccess.hint': '券已到账,可在「我的券」中查看',
'paymentSuccess.couponName': '券名称',
'paymentSuccess.payAmount': '支付金额',
'paymentSuccess.orderNo': '订单号',
'paymentSuccess.payTime': '支付时间',
'paymentSuccess.viewMyCoupon': '查看我的券',
'paymentSuccess.continueBrowse': '继续逛',
// ============ Search ============
'search.hint': '搜索券、品牌、分类...',
'search.cancel': '取消',
'search.hotSearch': '热门搜索',
'search.history': '搜索历史',
'search.clear': '清空',
'search.diningCoupon': '餐饮券',
'search.discountCoupon': '折扣券',
'search.travel': '旅游',
// ============ Redeem ============
'redeem.title': '出示券码',
'redeem.faceValue': '面值',
'redeem.validTime': '有效时间',
'redeem.refresh': '刷新券码',
'redeem.showHint': '请将此码出示给商户扫描,屏幕已自动调至最高亮度',
// ============ Sell Order ============
'sellOrder.title': '挂单出售',
'sellOrder.faceValue': '面值',
'sellOrder.credit': '信用',
'sellOrder.setPrice': '设定售价',
'sellOrder.price': '售价',
'sellOrder.aiSuggest': 'AI建议售价',
'sellOrder.bestDealRate': '此价格成交概率最高',
'sellOrder.discountRate': '折扣率',
'sellOrder.platformFee': '平台手续费 (1.5%)',
'sellOrder.estimatedReceive': '预计到账',
'sellOrder.marketAvg': '当前市场均价',
'sellOrder.recent24hTrades': '最近24小时成交',
'sellOrder.tradesUnit': '',
'sellOrder.confirmList': '确认挂单',
'sellOrder.success': '挂单成功',
'sellOrder.successHint': '您的券已挂到市场,当有买家下单时将自动成交。',
'sellOrder.ok': '确定',
// ============ Trading Page (My Trades) ============
'tradingPage.title': '我的交易',
'tradingPage.pendingOrders': '我的挂单',
'tradingPage.tradeRecords': '交易记录',
'tradingPage.listPrice': '挂单价',
'tradingPage.listTime': '挂单时间',
'tradingPage.cancelOrder': '撤单',
'tradingPage.buy': '买入',
'tradingPage.sell': '卖出',
// ============ Transfer ============
'transfer.title': '转赠给好友',
'transfer.searchFriend': '搜索好友(手机号/用户名)',
'transfer.confirmTransfer': '确认转赠',
'transfer.success': '转赠成功',
'transfer.confirm': '确定',
'transfer.cancel': '取消',
// ============ Wallet ============
'wallet.myBalance': '我的余额',
'wallet.totalBalance': '总余额',
'wallet.withdrawable': '可提现',
'wallet.frozen': '冻结中',
'wallet.deposit': '充值',
'wallet.withdraw': '提现',
'wallet.records': '交易记录',
'wallet.filter': '筛选',
'wallet.buyIn': '买入',
'wallet.sellOut': '卖出',
'wallet.giftTransfer': '转赠',
'wallet.redeemUse': '核销',
'deposit.title': '充值',
'deposit.currentBalance': '当前余额',
'deposit.amount': '充值金额',
'deposit.custom': '自定义金额',
'deposit.paymentMethod': '支付方式',
'deposit.submit': '充值',
'withdraw.title': '提现',
'withdraw.availableBalance': '可提现余额',
'withdraw.amount': '提现金额',
'withdraw.all': '全部',
'withdraw.to': '提现到',
'withdraw.savingsAccount': '储蓄账户',
'withdraw.fee': '手续费 (0.5%)',
'withdraw.actualReceive': '实际到账',
'withdraw.estimateTime': '预计 1-2 个工作日到账',
'withdraw.submit': '确认提现',
'txRecords.title': '交易记录',
'txRecords.all': '全部',
'txRecords.buy': '购买',
'txRecords.sell': '出售',
'txRecords.transfer': '转赠',
'txRecords.noRecords': '暂无记录',
'txRecords.orderNo': '订单号',
'txRecords.transferTo': '转赠给',
// ============ Profile ============
'profile.favorites': '收藏',
'profile.orders': '订单',
'profile.coupons': '',
'profile.wallet': '钱包',
'profile.account': '账户',
'profile.trade': '交易',
'profile.settings': '设置',
'profile.holdCoupons': '持券',
'profile.saved': '节省',
'profile.credit': '信用',
'profile.creditScore': '信用积分',
'profile.myTrades': '我的交易',
'profile.walletBalance': '钱包余额',
'profile.paymentManage': '支付管理',
'profile.kyc': '身份认证',
'profile.proMode': '高级模式',
'profile.myFavorites': '我的收藏',
'profile.securitySettings': '安全设置',
'profile.advancedSettings': '高级设置',
'profile.aboutGenex': '关于 Genex',
'profile.helpCenter': '帮助中心',
'profile.issuerPortal': '发行方入口',
'profile.merchantPortal': '商户入口',
'profile.simplifiedChinese': '简体中文',
'profile.logout': '退出登录',
// ============ Settings ============
'settings.title': '设置',
'settings.accountSecurity': '账号与安全',
'settings.phone': '手机号',
'settings.email': '邮箱',
'settings.changePassword': '修改密码',
'settings.identity': '身份认证',
'settings.paymentManage': '支付管理',
'settings.paymentMethod': '支付方式',
'settings.bankAccount': '银行账户',
'settings.paymentPassword': '支付密码',
'settings.notifications': '通知设置',
'settings.tradeNotify': '交易通知',
'settings.expiryRemind': '到期提醒',
'settings.marketChange': '行情变动',
'settings.marketingPush': '营销推送',
'settings.general': '通用',
'settings.language': '语言',
'settings.currency': '货币',
'settings.clearCache': '清除缓存',
'settings.about': '关于',
'settings.version': '版本',
'settings.userAgreement': '用户协议',
'settings.privacyPolicy': '隐私政策',
'settings.helpCenter': '帮助中心',
'settings.logout': '退出登录',
'settings.selectCurrency': '选择计价货币',
'settings.currencyNote': '此设置影响交易页面中所有价格的计价货币显示',
'settings.selectLanguage': '选择语言',
'settings.currencySymbol': '符号',
// ============ KYC ============
'kyc.title': '身份认证',
'kyc.currentLevel': '当前认证等级',
'kyc.l1Title': 'L1 基础认证',
'kyc.l1Desc': '手机号 + 邮箱验证',
'kyc.l1Limit': '每日购买限额 \$500',
'kyc.l1Feature': '可购买券、出示核销',
'kyc.l2Title': 'L2 身份认证',
'kyc.l2Desc': '身份证/护照验证',
'kyc.l2Limit': '每日购买限额 \$5,000',
'kyc.l2Feature': '解锁二级市场交易、P2P转赠',
'kyc.l3Title': 'L3 高级认证',
'kyc.l3Desc': '视频面审 + 地址证明',
'kyc.l3Limit': '无限额',
'kyc.l3Feature': '解锁大额交易、提现无限制',
'kyc.completed': '已完成',
'kyc.goVerify': '去认证',
'kyc.badgeLabel': '认证',
// ============ Payment Management ============
'payManage.title': '支付管理',
'payManage.myCards': '我的银行卡',
'payManage.addCard': '添加新银行卡',
'payManage.bankAccount': '银行账户(提现用)',
'payManage.paymentSecurity': '支付安全',
'payManage.paymentPassword': '支付密码',
'payManage.passwordSet': '已设置',
'payManage.biometricPay': '指纹/面容支付',
'payManage.biometricEnabled': '已开启',
'payManage.noPasswordPay': '免密支付',
'payManage.noPasswordLimit': '单笔≤\$10',
// ============ AI Chat ============
'aiChat.title': 'AI 助手',
'aiChat.greeting': '你好!我是 Genex AI 助手,可以帮你发现高性价比好券、比价分析、组合推荐。试试问我:',
'aiChat.suggest1': '推荐适合我的券',
'aiChat.suggest2': '星巴克券值不值得买?',
'aiChat.suggest3': '帮我做比价分析',
'aiChat.suggest4': '我的券快到期了怎么办?',
'aiChat.inputHint': '问我任何关于券的问题...',
'aiChat.confirmAction': '确认执行',
'aiChat.riskLow': '低风险',
'aiChat.riskNormal': '需确认',
'aiChat.riskHigh': '高风险',
// ============ AI Fab ============
'aiFab.title': 'AI 助手',
'aiFab.greeting': '你好!我是 Genex AI 助手,可以帮你管理券资产、查找优惠、分析价格。有什么需要帮助的吗?',
'aiFab.inputHint': '输入消息...',
'aiFab.suggest1': '帮我找高折扣券',
'aiFab.suggest2': '我的券快到期了吗?',
'aiFab.suggest3': '推荐今日好券',
'aiFab.suggest4': '分析我的券资产',
// ============ Messages ============
'message.title': '消息',
'message.markAllRead': '全部已读',
'message.tabTrade': '交易',
'message.tabExpiry': '到期',
'message.tabAnnouncement': '公告',
'message.detailTitle': '消息详情',
'message.tradeNotify': '交易通知',
'message.expiryRemind': '到期提醒',
'message.systemNotify': '系统通知',
'message.promoNotify': '活动推送',
'message.tradeSuccess': '交易成功通知',
'message.couponName': '券名称',
'message.faceValue': '面值',
'message.payAmount': '支付金额',
'message.orderNo': '订单号',
'message.payMethod': '支付方式',
'message.viewCouponDetail': '查看券详情',
// ============ Status Tags ============
'status.active': '可使用',
'status.pending': '待核销',
'status.expired': '已过期',
'status.used': '已使用',
'status.processing': '处理中',
'status.completed': '已完成',
'status.cancelled': '已取消',
'status.refunding': '退款中',
'status.onSale': '出售中',
// ============ Empty States ============
'empty.noCoupons': '还没有券',
'empty.noCouponsHint': '去市场看看有什么好券吧',
'empty.browse': '去逛逛',
'empty.noTrades': '暂无交易记录',
'empty.noTradesHint': '完成首笔交易后这里会显示记录',
'empty.noResults': '没有找到结果',
'empty.noResultsHint': '换个关键词试试',
'empty.noMessages': '暂无消息',
'empty.noMessagesHint': '交易通知和系统公告会显示在这里',
'empty.networkError': '网络连接失败',
'empty.networkErrorHint': '请检查网络设置后重试',
// ============ Coupon Card (shared widget) ============
'couponCard.expiredText': '已过期',
'couponCard.expiringToday': '今天到期',
'couponCard.daysToExpiry': '天后到期',
'couponCard.expiryFormat': '到期',
// ============ Issuer ============
'issuer.title': '发行方管理',
'issuer.verified': '已认证发行方',
'issuer.overview': '总览',
'issuer.issue': '发券',
'issuer.redeem': '核销',
'issuer.finance': '财务',
'issuer.more': '更多',
'issuer.totalIssued': '发行总量',
'issuer.totalSold': '已售出',
'issuer.totalRedeemed': '已核销',
'issuer.redeemRate': '核销率',
'issuer.quickActions': '快捷操作',
'issuer.createCoupon': '创建券',
'issuer.storeManage': '门店管理',
'issuer.salesAnalysis': '销售分析',
'issuer.statement': '对账单',
'issuer.myCoupons': '我的券',
'issuer.listed': '已上架',
'issuer.underReview': '审核中',
'issuer.soldOut': '已售罄',
'issuer.unlisted': '已下架',
'issuer.issuedSlash': '发行',
'issuer.sold': '已售',
'issuer.issueCenter': '发券中心',
'issuer.selectTemplate': '选择券模板',
'issuer.voucherType': '满减券',
'issuer.discountType': '折扣券',
'issuer.giftCardType': '礼品卡',
'issuer.storedValueType': '储值券',
'issuer.couponManage': '券管理',
'issuer.viewAll': '查看全部',
'issuer.couponEvents': '券活动',
'issuer.createNew': '创建新券',
'issuer.redeemManage': '核销管理',
'issuer.redeemTrend': '核销趋势',
'issuer.allStores': '全部门店',
'issuer.employees': '名员工',
'issuer.financeManage': '财务管理',
'issuer.totalSales': '总销售额',
'issuer.settled': '已到账',
'issuer.pendingSettle': '待结算',
'issuer.breakage': 'Breakage',
'issuer.withdrawBtn': '提现',
'issuer.reportBtn': '对账报表',
'issuer.settleDetail': '结算明细',
'issuer.creditLevel': '信用等级',
'issuer.issueQuota': '发行额度',
'issuer.usedQuota': '已用额度',
'issuer.dataCenter': '数据中心',
'issuer.issueSalesRate': '发行量/销量/兑付率',
'issuer.userProfile': '用户画像',
'issuer.userProfileDesc': '购买用户分布分析',
'issuer.creditDetail': '信用详情',
'issuer.creditDetailDesc': '评分详情与提升建议',
'issuer.quotaChange': '额度变动',
'issuer.quotaChangeDesc': '历史额度调整记录',
'issuer.companyInfo': '企业信息',
'issuer.companyInfoDesc': '营业执照/联系人',
'issuer.settingsItem': '设置',
'issuer.settingsItemDesc': '通知/安全/语言',
'issuer.helpItem': '帮助中心',
'issuer.helpItemDesc': '常见问题与客服',
// ============ Merchant ============
'merchant.today': '今日',
'merchant.onlineMode': '在线模式',
'merchant.offlineMode': '离线模式',
'merchant.pendingSync': '待同步',
'merchant.syncUnit': '',
'merchant.scanHint': '将券二维码对准扫描框',
'merchant.flashlight': '手电筒',
'merchant.manualInput': '手动输码',
'merchant.redeemRecords': '核销记录',
'merchant.storeData': '门店数据',
'merchant.inputCode': '手动输入券码',
'merchant.inputCodeHint': '请输入券码',
'merchant.query': '查询',
'merchant.userNickname': '用户昵称',
'merchant.consumer': '消费者',
'merchant.couponName': '券名称',
'merchant.faceValue': '面值',
'merchant.validUntil': '有效期',
'merchant.useCondition': '使用条件',
'merchant.noMinSpend': '无最低消费',
'merchant.confirmRedeem': '确认核销',
'merchant.redeemSuccess': '核销成功',
'merchant.continueRedeem': '继续核销',
'merchant.synced': '已同步',
'merchant.pendingSyncLabel': '待同步',
'merchant.redeemOperator': '核销员',
'merchant.todayRedeem': '今日核销',
'merchant.redeemAmount': '核销金额',
'merchant.weekTrend': '本周趋势',
'merchant.operatorRank': '核销员排行',
// ============ Merchant AI ============
'merchantAi.title': 'AI 智能助手',
'merchantAi.redeemAssist': '核销辅助',
'merchantAi.trafficForecast': '客流预测',
'merchantAi.anomalyAlert': '异常预警',
'merchantAi.verifyAuth': '验券真伪',
'merchantAi.checkStatus': '查券状态',
'merchantAi.batchRedeem': '批量核销',
'merchantAi.feedback': '问题反馈',
'merchantAi.quickActions': 'AI 快捷操作',
'merchantAi.redeemTips': '核销提示',
'merchantAi.todayHotRedeem': '今日热门核销',
'merchantAi.countUnit': '',
'merchantAi.aiMarketing': 'AI 营销建议',
'merchantAi.crossSellTitle': '推荐搭配销售',
'merchantAi.crossSellDesc': '购买咖啡券的顾客同时对糕点券感兴趣,建议推荐组合',
'merchantAi.weekendPromoTitle': '周末促销建议',
'merchantAi.weekendPromoDesc': '历史数据显示周六核销量+30%,建议推出周末限时活动',
'merchantAi.todayForecast': '今日客流预测',
'merchantAi.expectedRedeem': '预计核销',
'merchantAi.peakHours': '高峰时段',
'merchantAi.expectedRevenue': '预计收入',
'merchantAi.trafficInsight': '较上周同期增长12%建议午间增加1名收银员',
'merchantAi.hourlyForecast': '分时段预测',
'merchantAi.weeklyForecast': '本周预测',
'merchantAi.staffSuggestion': '排班建议',
'merchantAi.pendingCount': '待处理',
'merchantAi.resolvedToday': '今日已处理',
'merchantAi.riskIndex': '风险指数',
'merchantAi.riskLow': '',
'merchantAi.activeAlerts': '活跃预警',
'merchantAi.highFreqRedeem': '高频核销检测',
'merchantAi.suspectFakeCode': '疑似伪造券码',
'merchantAi.suspiciousPatterns': '可疑模式检测',
'merchantAi.consecutiveRedeem': '同一用户连续核销',
'merchantAi.offHoursRedeem': '非营业时间核销尝试',
'merchantAi.expiredRedeemAttempt': '过期券核销尝试',
'merchantAi.statusAbnormal': '异常',
'merchantAi.statusWarning': '注意',
'merchantAi.statusNormal': '正常',
'merchantAi.expiredBlock': '过期券核销拦截',
'merchantAi.duplicateBlock': '重复核销拦截',
'merchantAi.wrongStoreAlert': '非本店券提醒',
'merchantAi.insufficientBalance': '余额不足核销',
'merchantAi.systemRetry': '系统超时重试',
'merchantAi.monday': '周一',
'merchantAi.tuesday': '周二',
'merchantAi.wednesday': '周三',
'merchantAi.thursday': '周四',
'merchantAi.friday': '周五',
'merchantAi.saturday': '周六',
'merchantAi.sunday': '周日',
// ============ Pro Mode ============
'proMode.title': '高级模式',
'proMode.toggleDesc': '开启后可查看链上信息和连接外部钱包',
'proMode.requireKycL2': '需要 KYC L2 及以上认证',
'proMode.connected': '已连接',
'proMode.disconnect': '断开',
'proMode.connectWallet': '连接外部钱包',
'proMode.walletDesc': '连接外部钱包后可将平台资产提取至自有地址',
'proMode.showChainAddress': '显示链上地址',
'proMode.showChainAddressDesc': '在券详情中展示合约地址',
'proMode.showTxHash': '显示交易Hash',
'proMode.showTxHashDesc': '在交易记录中展示链上Hash',
'proMode.txExplorer': '交易浏览器',
'proMode.txBuyExample': '购买 星巴克 \$25 礼品卡',
'proMode.txSellExample': '出售 Amazon \$100 券',
'proMode.confirmed': '已确认',
'proMode.confirming': '确认中',
'proMode.viewAllTx': '查看全部链上交易',
'proMode.chainAssets': '链上资产',
'proMode.custodialWallet': '平台托管钱包',
'proMode.externalWallet': '外部钱包 (MetaMask)',
'proMode.couponCount5': '5 张券',
'proMode.couponCount0': '0 张券',
'proMode.extractToWallet': '提取至外部钱包',
'proMode.tradeTrack': '交易轨道',
'proMode.utilityTrackDesc': '券有效期≤12个月无需证券牌照',
'proMode.securitiesTrackDesc': '长期投资型券产品(即将推出)',
'proMode.mvpNote': '当前MVP版本仅支持Utility Track',
'proMode.comingSoon': '敬请期待',
'proMode.whatIsTitle': '什么是高级模式?',
'proMode.whatIsDesc': '高级模式面向有区块链经验的用户,开启后可以:\n'
'• 连接外部钱包MetaMask等\n'
'• 查看链上地址和交易Hash\n'
'• 将资产提取至自有钱包\n'
'• 查看底层链上数据\n\n'
'需要完成 KYC L2 认证后方可开启。',
// ============ Receive Coupon ============
'receiveCoupon.title': '接收券',
'receiveCoupon.hint': '向他人展示下方二维码或接收ID对方可通过扫码或输入ID将券转赠到你的钱包。',
'receiveCoupon.id': '接收ID',
'receiveCoupon.idCopied': '接收ID已复制到剪贴板',
'receiveCoupon.note': '接收的券将自动存入你的钱包,可在首页钱包中查看和管理。',
};

View File

@ -0,0 +1,686 @@
const Map<String, String> zhTW = {
// ============ Common ============
'common.confirm': '確認',
'common.cancel': '取消',
'common.save': '儲存',
'common.delete': '刪除',
'common.edit': '編輯',
'common.search': '搜尋',
'common.loading': '載入中...',
'common.retry': '重試',
'common.done': '完成',
'common.next': '下一步',
'common.back': '返回',
'common.close': '關閉',
'common.more': '更多',
'common.all': '全部',
'common.filter': '篩選',
'common.sort': '排序',
'common.copy': '複製',
'common.copied': '已複製到剪貼簿',
'common.today': '今日',
'common.thisWeek': '本週',
'common.thisMonth': '本月',
// ============ Navigation ============
'nav.home': '首頁',
'nav.market': '市場',
'nav.myCoupons': '我的券',
'nav.messages': '訊息',
'nav.profile': '我的',
// ============ Welcome / Auth ============
'welcome.slogan': '讓每一張券都有價值',
'welcome.phoneRegister': '手機號註冊',
'welcome.emailRegister': '信箱註冊',
'welcome.otherLogin': '其他方式登入',
'welcome.hasAccount': '已有帳號?',
'welcome.login': '登入',
'welcome.agreement': '註冊即表示同意《使用者協議》和《隱私權政策》',
'login.title': '歡迎回來',
'login.subtitle': '登入 Genex 管理你的券資產',
'login.passwordTab': '密碼登入',
'login.codeTab': '驗證碼登入',
'login.phoneOrEmail': '手機號或信箱',
'login.password': '密碼',
'login.forgotPassword': '忘記密碼?',
'login.submit': '登入',
'login.phone': '手機號',
'login.verifyCode': '驗證碼',
'login.getCode': '取得驗證碼',
'register.title': '建立帳號',
'register.emailSubtitle': '使用信箱註冊 Genex 帳號',
'register.phoneSubtitle': '使用手機號註冊 Genex 帳號',
'register.email': '信箱地址',
'register.phone': '手機號',
'register.emailHint': '請輸入信箱地址',
'register.phoneHint': '請輸入手機號',
'register.code': '驗證碼',
'register.codeHint': '請輸入6位驗證碼',
'register.getCode': '取得驗證碼',
'register.setPassword': '設定密碼',
'register.passwordHint': '8-20位含字母和數字',
'register.agreement': '我已閱讀並同意',
'register.userAgreement': '《使用者協議》',
'register.privacyPolicy': '《隱私權政策》',
'register.submit': '註冊',
'register.stepVerify': '驗證',
'register.stepPassword': '設密碼',
'register.stepDone': '完成',
'register.rule8chars': '8位以上',
'register.ruleLetter': '含字母',
'register.ruleNumber': '含數字',
'forgot.title': '找回密碼',
'forgot.inputAccount': '輸入手機號或信箱',
'forgot.sendHint': '我們將向您發送驗證碼',
'forgot.accountHint': '手機號 / 信箱地址',
'forgot.getCode': '取得驗證碼',
'forgot.inputCode': '輸入驗證碼',
'forgot.codeSentTo': '驗證碼已發送至',
'forgot.codeHint': '6位驗證碼',
'forgot.resend': '重新發送',
'forgot.next': '下一步',
'forgot.setNewPassword': '設定新密碼',
'forgot.newPasswordHint': '請輸入新密碼8位以上',
'forgot.newPassword': '新密碼',
'forgot.confirmPassword': '確認新密碼',
'forgot.confirmChange': '確認修改',
'forgot.success': '密碼修改成功',
'forgot.successHint': '請使用新密碼登入',
'forgot.backToLogin': '返回登入',
// ============ Home ============
'home.searchHint': '搜尋券、品牌、分類...',
'home.dining': '餐飲',
'home.shopping': '購物',
'home.entertainment': '娛樂',
'home.travel': '出行',
'home.lifestyle': '生活',
'home.brand': '品牌',
'home.discount': '折扣',
'home.allCategories': '全部',
'home.featuredCoupons': '精選好券',
'home.viewAllCoupons': '查看全部',
'home.aiRecommend': 'AI 推薦',
'home.aiRecommendDesc': '根據你的偏好發現了3張高性價比券',
'home.bannerNewUser': '新用戶專享',
'home.bannerNewUserDesc': '首單立減 \$10',
'home.bannerDiscount': '限時折扣',
'home.bannerDiscountDesc': '全場低至7折',
'home.bannerHot': '熱門推薦',
'home.bannerHotDesc': '精選高折扣券',
// ============ Market ============
'market.title': '交易市場',
'market.primary': '一級市場(全新)',
'market.secondary': '二級市場(轉售)',
'market.dining': '餐飲',
'market.shopping': '購物',
'market.entertainment': '娛樂',
'market.travel': '出行',
'market.lifestyle': '生活',
'market.sports': '運動',
'market.discountRate': '折扣率',
'market.priceUp': '價格↑',
'market.priceDown': '價格↓',
'market.expiryDate': '到期時間',
'market.issuePrice': '發行價',
'market.faceValue': '面值',
'market.discount': '折扣',
'market.totalSupply': '發行量',
'market.salesProgress': '銷售進度',
'market.upcoming': '即將開始',
'market.subscribing': '申購中',
'market.ended': '已結束',
'market.timeToStart': '距開始',
'market.couponBrand': '券名/品牌',
'market.latestPrice': '最新價',
'market.change24h': '24h漲跌',
'market.discountSuffix': '',
// ============ My Coupons ============
'myCoupons.title': '我的券',
'myCoupons.usable': '可使用',
'myCoupons.pendingRedeem': '待核銷',
'myCoupons.expired': '已過期',
'myCoupons.faceValue': '面值',
'myCoupons.transfer': '轉贈',
'myCoupons.sell': '出售',
'myCoupons.expiredText': '已過期',
'myCoupons.expiringToday': '今天到期',
'myCoupons.daysToExpiry': '天後到期',
// ============ Coupon Detail ============
'couponDetail.title': '券詳情',
'couponDetail.favorite': '收藏',
'couponDetail.buyNow': '立即購買',
'couponDetail.saveBadge': '比面值節省',
'couponDetail.faceValue': '面值',
'couponDetail.validUntil': '有效期',
'couponDetail.type': '類型',
'couponDetail.issuer': '發行方',
'couponDetail.consumeCoupon': '消費券',
'couponDetail.usageNote': '使用說明',
'couponDetail.allStores': '全國門市通用',
'couponDetail.canTransfer': '可轉贈給好友',
'couponDetail.useAnytime': '有效期內隨時使用',
'couponDetail.noStack': '不可疊加使用',
'couponDetail.noCash': '不可兌換現金',
'couponDetail.stores': '使用門市',
'couponDetail.storeCount': '全國 12,800+ 門市',
'couponDetail.storeDesc': '支援全國所有直營門市使用',
'couponDetail.priceTrend': '價格走勢',
'couponDetail.last30Days': '近30天',
'couponDetail.highest': '最高',
'couponDetail.lowest': '最低',
'couponDetail.average': '均價',
'couponDetail.tradeHistory': '歷史成交',
'couponDetail.nearbyStores': '附近可用門市',
'couponDetail.distance': '距離',
'couponDetail.open': '營業中',
'couponDetail.similar': '同類券推薦',
// ============ My Coupon Detail ============
'myCoupon.title': '券詳情',
'myCoupon.active': '可使用',
'myCoupon.showQrHint': '出示此二維碼給商戶掃描核銷',
'myCoupon.switchBarcode': '切換條碼',
'myCoupon.faceValue': '面值',
'myCoupon.purchasePrice': '購買價格',
'myCoupon.validUntil': '有效期',
'myCoupon.orderNo': '訂單號',
'myCoupon.resellCount': '剩餘可轉售次數',
'myCoupon.transfer': '轉贈',
'myCoupon.sell': '出售',
'myCoupon.usageNote': '使用說明',
'myCoupon.useInStore': '全國門市通用',
'myCoupon.useInTime': '請在有效期內使用',
'myCoupon.onePerVisit': '每次消費僅可使用一張',
'myCoupon.noCash': '不可兌換現金',
'myCoupon.extractToWallet': '提取到外部錢包',
'myCoupon.requireKycL2': '需KYC L2+認證',
'myCoupon.viewTrades': '查看交易紀錄',
'myCoupon.help': '使用幫助',
// ============ Order Confirm ============
'orderConfirm.title': '確認訂單',
'orderConfirm.quantity': '購買數量',
'orderConfirm.paymentMethod': '支付方式',
'orderConfirm.bankCard': '銀行卡/信用卡',
'orderConfirm.priceDetail': '價格明細',
'orderConfirm.buyingNote': '您正在購買消費券用於消費',
'orderConfirm.total': '合計',
'orderConfirm.confirmPay': '確認支付',
'orderConfirm.unitPrice': '單價',
'orderConfirm.count': '數量',
'orderConfirm.saveBadge': '比面值節省',
'orderConfirm.biometricHint': '請驗證指紋或臉部辨識以完成支付',
'orderConfirm.usePasswordPay': '使用密碼支付',
// ============ Payment ============
'payment.title': '選擇支付方式',
'payment.addNew': '新增支付方式',
'payment.confirmPay': '確認支付',
'payment.bankTransfer': '銀行轉帳',
'paymentSuccess.title': '支付成功',
'paymentSuccess.hint': '券已到帳,可在「我的券」中查看',
'paymentSuccess.couponName': '券名稱',
'paymentSuccess.payAmount': '支付金額',
'paymentSuccess.orderNo': '訂單號',
'paymentSuccess.payTime': '支付時間',
'paymentSuccess.viewMyCoupon': '查看我的券',
'paymentSuccess.continueBrowse': '繼續逛',
// ============ Search ============
'search.hint': '搜尋券、品牌、分類...',
'search.cancel': '取消',
'search.hotSearch': '熱門搜尋',
'search.history': '搜尋歷史',
'search.clear': '清空',
'search.diningCoupon': '餐飲券',
'search.discountCoupon': '折扣券',
'search.travel': '旅遊',
// ============ Redeem ============
'redeem.title': '出示券碼',
'redeem.faceValue': '面值',
'redeem.validTime': '有效時間',
'redeem.refresh': '重新整理券碼',
'redeem.showHint': '請將此碼出示給商戶掃描,螢幕已自動調至最高亮度',
// ============ Sell Order ============
'sellOrder.title': '掛單出售',
'sellOrder.faceValue': '面值',
'sellOrder.credit': '信用',
'sellOrder.setPrice': '設定售價',
'sellOrder.price': '售價',
'sellOrder.aiSuggest': 'AI建議售價',
'sellOrder.bestDealRate': '此價格成交機率最高',
'sellOrder.discountRate': '折扣率',
'sellOrder.platformFee': '平台手續費 (1.5%)',
'sellOrder.estimatedReceive': '預計到帳',
'sellOrder.marketAvg': '目前市場均價',
'sellOrder.recent24hTrades': '最近24小時成交',
'sellOrder.tradesUnit': '',
'sellOrder.confirmList': '確認掛單',
'sellOrder.success': '掛單成功',
'sellOrder.successHint': '您的券已掛到市場,當有買家下單時將自動成交。',
'sellOrder.ok': '確定',
// ============ Trading Page ============
'tradingPage.title': '我的交易',
'tradingPage.pendingOrders': '我的掛單',
'tradingPage.tradeRecords': '交易紀錄',
'tradingPage.listPrice': '掛單價',
'tradingPage.listTime': '掛單時間',
'tradingPage.cancelOrder': '撤單',
'tradingPage.buy': '買入',
'tradingPage.sell': '賣出',
// ============ Transfer ============
'transfer.title': '轉贈給好友',
'transfer.searchFriend': '搜尋好友(手機號/使用者名稱)',
'transfer.confirmTransfer': '確認轉贈',
'transfer.success': '轉贈成功',
'transfer.confirm': '確定',
'transfer.cancel': '取消',
// ============ Wallet ============
'wallet.myBalance': '我的餘額',
'wallet.totalBalance': '總餘額',
'wallet.withdrawable': '可提現',
'wallet.frozen': '凍結中',
'wallet.deposit': '儲值',
'wallet.withdraw': '提現',
'wallet.records': '交易紀錄',
'wallet.filter': '篩選',
'wallet.buyIn': '買入',
'wallet.sellOut': '賣出',
'wallet.giftTransfer': '轉贈',
'wallet.redeemUse': '核銷',
'deposit.title': '儲值',
'deposit.currentBalance': '目前餘額',
'deposit.amount': '儲值金額',
'deposit.custom': '自訂金額',
'deposit.paymentMethod': '支付方式',
'deposit.submit': '儲值',
'withdraw.title': '提現',
'withdraw.availableBalance': '可提現餘額',
'withdraw.amount': '提現金額',
'withdraw.all': '全部',
'withdraw.to': '提現到',
'withdraw.savingsAccount': '儲蓄帳戶',
'withdraw.fee': '手續費 (0.5%)',
'withdraw.actualReceive': '實際到帳',
'withdraw.estimateTime': '預計 1-2 個工作日到帳',
'withdraw.submit': '確認提現',
'txRecords.title': '交易紀錄',
'txRecords.all': '全部',
'txRecords.buy': '購買',
'txRecords.sell': '出售',
'txRecords.transfer': '轉贈',
'txRecords.noRecords': '暫無紀錄',
'txRecords.orderNo': '訂單號',
'txRecords.transferTo': '轉贈給',
// ============ Profile ============
'profile.favorites': '收藏',
'profile.orders': '訂單',
'profile.coupons': '',
'profile.wallet': '錢包',
'profile.account': '帳戶',
'profile.trade': '交易',
'profile.settings': '設定',
'profile.holdCoupons': '持券',
'profile.saved': '節省',
'profile.credit': '信用',
'profile.creditScore': '信用積分',
'profile.myTrades': '我的交易',
'profile.walletBalance': '錢包餘額',
'profile.paymentManage': '支付管理',
'profile.kyc': '身分認證',
'profile.proMode': '進階模式',
'profile.myFavorites': '我的收藏',
'profile.securitySettings': '安全設定',
'profile.advancedSettings': '進階設定',
'profile.aboutGenex': '關於 Genex',
'profile.helpCenter': '幫助中心',
'profile.issuerPortal': '發行方入口',
'profile.merchantPortal': '商戶入口',
'profile.simplifiedChinese': '簡體中文',
'profile.logout': '登出',
// ============ Settings ============
'settings.title': '設定',
'settings.accountSecurity': '帳號與安全',
'settings.phone': '手機號',
'settings.email': '信箱',
'settings.changePassword': '修改密碼',
'settings.identity': '身分認證',
'settings.paymentManage': '支付管理',
'settings.paymentMethod': '支付方式',
'settings.bankAccount': '銀行帳戶',
'settings.paymentPassword': '支付密碼',
'settings.notifications': '通知設定',
'settings.tradeNotify': '交易通知',
'settings.expiryRemind': '到期提醒',
'settings.marketChange': '行情變動',
'settings.marketingPush': '行銷推播',
'settings.general': '一般',
'settings.language': '語言',
'settings.currency': '貨幣',
'settings.clearCache': '清除快取',
'settings.about': '關於',
'settings.version': '版本',
'settings.userAgreement': '使用者協議',
'settings.privacyPolicy': '隱私權政策',
'settings.helpCenter': '幫助中心',
'settings.logout': '登出',
'settings.selectCurrency': '選擇計價貨幣',
'settings.currencyNote': '此設定影響交易頁面中所有價格的計價貨幣顯示',
'settings.selectLanguage': '選擇語言',
'settings.currencySymbol': '符號',
// ============ KYC ============
'kyc.title': '身分認證',
'kyc.currentLevel': '目前認證等級',
'kyc.l1Title': 'L1 基礎認證',
'kyc.l1Desc': '手機號 + 信箱驗證',
'kyc.l1Limit': '每日購買限額 \$500',
'kyc.l1Feature': '可購買券、出示核銷',
'kyc.l2Title': 'L2 身分認證',
'kyc.l2Desc': '身分證/護照驗證',
'kyc.l2Limit': '每日購買限額 \$5,000',
'kyc.l2Feature': '解鎖二級市場交易、P2P轉贈',
'kyc.l3Title': 'L3 進階認證',
'kyc.l3Desc': '視訊面審 + 地址證明',
'kyc.l3Limit': '無限額',
'kyc.l3Feature': '解鎖大額交易、提現無限制',
'kyc.completed': '已完成',
'kyc.goVerify': '去認證',
'kyc.badgeLabel': '認證',
// ============ Payment Management ============
'payManage.title': '支付管理',
'payManage.myCards': '我的銀行卡',
'payManage.addCard': '新增銀行卡',
'payManage.bankAccount': '銀行帳戶(提現用)',
'payManage.paymentSecurity': '支付安全',
'payManage.paymentPassword': '支付密碼',
'payManage.passwordSet': '已設定',
'payManage.biometricPay': '指紋/臉部辨識支付',
'payManage.biometricEnabled': '已開啟',
'payManage.noPasswordPay': '免密支付',
'payManage.noPasswordLimit': '單筆≤\$10',
// ============ AI Chat ============
'aiChat.title': 'AI 助手',
'aiChat.greeting': '你好!我是 Genex AI 助手,可以幫你發現高性價比好券、比價分析、組合推薦。試試問我:',
'aiChat.suggest1': '推薦適合我的券',
'aiChat.suggest2': 'Starbucks 券值不值得買?',
'aiChat.suggest3': '幫我做比價分析',
'aiChat.suggest4': '我的券快到期了怎麼辦?',
'aiChat.inputHint': '問我任何關於券的問題...',
'aiChat.confirmAction': '確認執行',
'aiChat.riskLow': '低風險',
'aiChat.riskNormal': '需確認',
'aiChat.riskHigh': '高風險',
// ============ AI Fab ============
'aiFab.title': 'AI 助手',
'aiFab.greeting': '你好!我是 Genex AI 助手,可以幫你管理券資產、查找優惠、分析價格。有什麼需要幫助的嗎?',
'aiFab.inputHint': '輸入訊息...',
'aiFab.suggest1': '幫我找高折扣券',
'aiFab.suggest2': '我的券快到期了嗎?',
'aiFab.suggest3': '推薦今日好券',
'aiFab.suggest4': '分析我的券資產',
// ============ Messages ============
'message.title': '訊息',
'message.markAllRead': '全部已讀',
'message.tabTrade': '交易',
'message.tabExpiry': '到期',
'message.tabAnnouncement': '公告',
'message.detailTitle': '訊息詳情',
'message.tradeNotify': '交易通知',
'message.expiryRemind': '到期提醒',
'message.systemNotify': '系統通知',
'message.promoNotify': '活動推播',
'message.tradeSuccess': '交易成功通知',
'message.couponName': '券名稱',
'message.faceValue': '面值',
'message.payAmount': '支付金額',
'message.orderNo': '訂單號',
'message.payMethod': '支付方式',
'message.viewCouponDetail': '查看券詳情',
// ============ Status Tags ============
'status.active': '可使用',
'status.pending': '待核銷',
'status.expired': '已過期',
'status.used': '已使用',
'status.processing': '處理中',
'status.completed': '已完成',
'status.cancelled': '已取消',
'status.refunding': '退款中',
'status.onSale': '出售中',
// ============ Empty States ============
'empty.noCoupons': '還沒有券',
'empty.noCouponsHint': '去市場看看有什麼好券吧',
'empty.browse': '去逛逛',
'empty.noTrades': '暫無交易紀錄',
'empty.noTradesHint': '完成首筆交易後這裡會顯示紀錄',
'empty.noResults': '沒有找到結果',
'empty.noResultsHint': '換個關鍵字試試',
'empty.noMessages': '暫無訊息',
'empty.noMessagesHint': '交易通知和系統公告會顯示在這裡',
'empty.networkError': '網路連線失敗',
'empty.networkErrorHint': '請檢查網路設定後重試',
// ============ Coupon Card (shared widget) ============
'couponCard.expiredText': '已過期',
'couponCard.expiringToday': '今天到期',
'couponCard.daysToExpiry': '天後到期',
'couponCard.expiryFormat': '到期',
// ============ Issuer ============
'issuer.title': '發行方管理',
'issuer.verified': '已認證發行方',
'issuer.overview': '總覽',
'issuer.issue': '發券',
'issuer.redeem': '核銷',
'issuer.finance': '財務',
'issuer.more': '更多',
'issuer.totalIssued': '發行總量',
'issuer.totalSold': '已售出',
'issuer.totalRedeemed': '已核銷',
'issuer.redeemRate': '核銷率',
'issuer.quickActions': '快捷操作',
'issuer.createCoupon': '建立券',
'issuer.storeManage': '門市管理',
'issuer.salesAnalysis': '銷售分析',
'issuer.statement': '對帳單',
'issuer.myCoupons': '我的券',
'issuer.listed': '已上架',
'issuer.underReview': '審核中',
'issuer.soldOut': '已售罄',
'issuer.unlisted': '已下架',
'issuer.issuedSlash': '發行',
'issuer.sold': '已售',
'issuer.issueCenter': '發券中心',
'issuer.selectTemplate': '選擇券範本',
'issuer.voucherType': '滿減券',
'issuer.discountType': '折扣券',
'issuer.giftCardType': '禮品卡',
'issuer.storedValueType': '儲值券',
'issuer.couponManage': '券管理',
'issuer.viewAll': '查看全部',
'issuer.couponEvents': '券活動',
'issuer.createNew': '建立新券',
'issuer.redeemManage': '核銷管理',
'issuer.redeemTrend': '核銷趨勢',
'issuer.allStores': '全部門市',
'issuer.employees': '名員工',
'issuer.financeManage': '財務管理',
'issuer.totalSales': '總銷售額',
'issuer.settled': '已到帳',
'issuer.pendingSettle': '待結算',
'issuer.breakage': 'Breakage',
'issuer.withdrawBtn': '提現',
'issuer.reportBtn': '對帳報表',
'issuer.settleDetail': '結算明細',
'issuer.creditLevel': '信用等級',
'issuer.issueQuota': '發行額度',
'issuer.usedQuota': '已用額度',
'issuer.dataCenter': '數據中心',
'issuer.issueSalesRate': '發行量/銷量/兌付率',
'issuer.userProfile': '使用者畫像',
'issuer.userProfileDesc': '購買使用者分佈分析',
'issuer.creditDetail': '信用詳情',
'issuer.creditDetailDesc': '評分詳情與提升建議',
'issuer.quotaChange': '額度變動',
'issuer.quotaChangeDesc': '歷史額度調整紀錄',
'issuer.companyInfo': '企業資訊',
'issuer.companyInfoDesc': '營業執照/聯絡人',
'issuer.settingsItem': '設定',
'issuer.settingsItemDesc': '通知/安全/語言',
'issuer.helpItem': '幫助中心',
'issuer.helpItemDesc': '常見問題與客服',
// ============ Merchant ============
'merchant.today': '今日',
'merchant.onlineMode': '線上模式',
'merchant.offlineMode': '離線模式',
'merchant.pendingSync': '待同步',
'merchant.syncUnit': '',
'merchant.scanHint': '將券二維碼對準掃描框',
'merchant.flashlight': '手電筒',
'merchant.manualInput': '手動輸碼',
'merchant.redeemRecords': '核銷紀錄',
'merchant.storeData': '門市數據',
'merchant.inputCode': '手動輸入券碼',
'merchant.inputCodeHint': '請輸入券碼',
'merchant.query': '查詢',
'merchant.userNickname': '使用者暱稱',
'merchant.consumer': '消費者',
'merchant.couponName': '券名稱',
'merchant.faceValue': '面值',
'merchant.validUntil': '有效期',
'merchant.useCondition': '使用條件',
'merchant.noMinSpend': '無最低消費',
'merchant.confirmRedeem': '確認核銷',
'merchant.redeemSuccess': '核銷成功',
'merchant.continueRedeem': '繼續核銷',
'merchant.synced': '已同步',
'merchant.pendingSyncLabel': '待同步',
'merchant.redeemOperator': '核銷員',
'merchant.todayRedeem': '今日核銷',
'merchant.redeemAmount': '核銷金額',
'merchant.weekTrend': '本週趨勢',
'merchant.operatorRank': '核銷員排行',
// ============ Merchant AI ============
'merchantAi.title': 'AI 智能助手',
'merchantAi.redeemAssist': '核銷輔助',
'merchantAi.trafficForecast': '客流預測',
'merchantAi.anomalyAlert': '異常預警',
'merchantAi.verifyAuth': '驗券真偽',
'merchantAi.checkStatus': '查券狀態',
'merchantAi.batchRedeem': '批量核銷',
'merchantAi.feedback': '問題回報',
'merchantAi.quickActions': 'AI 快捷操作',
'merchantAi.redeemTips': '核銷提示',
'merchantAi.todayHotRedeem': '今日熱門核銷',
'merchantAi.countUnit': '',
'merchantAi.aiMarketing': 'AI 行銷建議',
'merchantAi.crossSellTitle': '推薦搭配銷售',
'merchantAi.crossSellDesc': '購買咖啡券的顧客同時對糕點券感興趣,建議推薦組合',
'merchantAi.weekendPromoTitle': '週末促銷建議',
'merchantAi.weekendPromoDesc': '歷史數據顯示週六核銷量+30%,建議推出週末限時活動',
'merchantAi.todayForecast': '今日客流預測',
'merchantAi.expectedRedeem': '預計核銷',
'merchantAi.peakHours': '高峰時段',
'merchantAi.expectedRevenue': '預計收入',
'merchantAi.trafficInsight': '較上週同期增長12%建議午間增加1名收銀員',
'merchantAi.hourlyForecast': '分時段預測',
'merchantAi.weeklyForecast': '本週預測',
'merchantAi.staffSuggestion': '排班建議',
'merchantAi.pendingCount': '待處理',
'merchantAi.resolvedToday': '今日已處理',
'merchantAi.riskIndex': '風險指數',
'merchantAi.riskLow': '',
'merchantAi.activeAlerts': '活躍預警',
'merchantAi.highFreqRedeem': '高頻核銷檢測',
'merchantAi.suspectFakeCode': '疑似偽造券碼',
'merchantAi.suspiciousPatterns': '可疑模式檢測',
'merchantAi.consecutiveRedeem': '同一使用者連續核銷',
'merchantAi.offHoursRedeem': '非營業時間核銷嘗試',
'merchantAi.expiredRedeemAttempt': '過期券核銷嘗試',
'merchantAi.statusAbnormal': '異常',
'merchantAi.statusWarning': '注意',
'merchantAi.statusNormal': '正常',
'merchantAi.expiredBlock': '過期券核銷攔截',
'merchantAi.duplicateBlock': '重複核銷攔截',
'merchantAi.wrongStoreAlert': '非本店券提醒',
'merchantAi.insufficientBalance': '餘額不足核銷',
'merchantAi.systemRetry': '系統逾時重試',
'merchantAi.monday': '週一',
'merchantAi.tuesday': '週二',
'merchantAi.wednesday': '週三',
'merchantAi.thursday': '週四',
'merchantAi.friday': '週五',
'merchantAi.saturday': '週六',
'merchantAi.sunday': '週日',
// ============ Pro Mode ============
'proMode.title': '高級模式',
'proMode.toggleDesc': '開啟後可查看鏈上資訊和連接外部錢包',
'proMode.requireKycL2': '需要 KYC L2 及以上認證',
'proMode.connected': '已連接',
'proMode.disconnect': '斷開',
'proMode.connectWallet': '連接外部錢包',
'proMode.walletDesc': '連接外部錢包後可將平台資產提取至自有地址',
'proMode.showChainAddress': '顯示鏈上地址',
'proMode.showChainAddressDesc': '在券詳情中展示合約地址',
'proMode.showTxHash': '顯示交易Hash',
'proMode.showTxHashDesc': '在交易記錄中展示鏈上Hash',
'proMode.txExplorer': '交易瀏覽器',
'proMode.txBuyExample': '購買 星巴克 \$25 禮品卡',
'proMode.txSellExample': '出售 Amazon \$100 券',
'proMode.confirmed': '已確認',
'proMode.confirming': '確認中',
'proMode.viewAllTx': '查看全部鏈上交易',
'proMode.chainAssets': '鏈上資產',
'proMode.custodialWallet': '平台託管錢包',
'proMode.externalWallet': '外部錢包 (MetaMask)',
'proMode.couponCount5': '5 張券',
'proMode.couponCount0': '0 張券',
'proMode.extractToWallet': '提取至外部錢包',
'proMode.tradeTrack': '交易軌道',
'proMode.utilityTrackDesc': '券有效期≤12個月無需證券牌照',
'proMode.securitiesTrackDesc': '長期投資型券產品(即將推出)',
'proMode.mvpNote': '當前MVP版本僅支持Utility Track',
'proMode.comingSoon': '敬請期待',
'proMode.whatIsTitle': '什麼是高級模式?',
'proMode.whatIsDesc': '高級模式面向有區塊鏈經驗的使用者,開啟後可以:\n'
'• 連接外部錢包MetaMask等\n'
'• 查看鏈上地址和交易Hash\n'
'• 將資產提取至自有錢包\n'
'• 查看底層鏈上數據\n\n'
'需要完成 KYC L2 認證後方可開啟。',
// ============ Receive Coupon ============
'receiveCoupon.title': '接收券',
'receiveCoupon.hint': '向他人展示下方二維碼或接收ID對方可透過掃碼或輸入ID將券轉贈到你的錢包。',
'receiveCoupon.id': '接收ID',
'receiveCoupon.idCopied': '接收ID已複製到剪貼簿',
'receiveCoupon.note': '接收的券將自動存入你的錢包,可在首頁錢包中查看和管理。',
};

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../app/i18n/app_localizations.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 '../features/coupons/presentation/pages/home_page.dart'; import '../features/coupons/presentation/pages/home_page.dart';
@ -44,20 +45,20 @@ class _MainShellState extends State<MainShell> {
selectedIndex: _currentIndex, selectedIndex: _currentIndex,
onDestinationSelected: (index) => setState(() => _currentIndex = index), onDestinationSelected: (index) => setState(() => _currentIndex = index),
destinations: [ destinations: [
_buildDestination(Icons.home_rounded, Icons.home_outlined, '首页'), _buildDestination(Icons.home_rounded, Icons.home_outlined, context.t('nav.home')),
_buildDestination(Icons.storefront_rounded, Icons.storefront_outlined, '市场'), _buildDestination(Icons.storefront_rounded, Icons.storefront_outlined, context.t('nav.market')),
_buildDestination( _buildDestination(
Icons.confirmation_number_rounded, Icons.confirmation_number_rounded,
Icons.confirmation_number_outlined, Icons.confirmation_number_outlined,
'我的券', context.t('nav.myCoupons'),
), ),
_buildBadgeDestination( _buildBadgeDestination(
Icons.notifications_rounded, Icons.notifications_rounded,
Icons.notifications_outlined, Icons.notifications_outlined,
'消息', context.t('nav.messages'),
2, 2,
), ),
_buildDestination(Icons.person_rounded, Icons.person_outlined, '我的'), _buildDestination(Icons.person_rounded, Icons.person_outlined, context.t('nav.profile')),
], ],
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -16,21 +17,38 @@ class AgentChatPage extends StatefulWidget {
class _AgentChatPageState extends State<AgentChatPage> { class _AgentChatPageState extends State<AgentChatPage> {
final _controller = TextEditingController(); final _controller = TextEditingController();
final _scrollController = ScrollController(); final _scrollController = ScrollController();
final List<_Msg> _messages = [ late final List<_Msg> _messages;
_Msg(true, '你好!我是 Genex AI 助手,可以帮你发现高性价比好券、比价分析、组合推荐。试试问我:'), late final List<String> _suggestions;
];
final _suggestions = ['推荐适合我的券', '星巴克券值不值得买?', '帮我做比价分析', '我的券快到期了怎么办?']; @override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_initialized) {
_messages = [
_Msg(true, context.t('aiChat.greeting')),
];
_suggestions = [
context.t('aiChat.suggest1'),
context.t('aiChat.suggest2'),
context.t('aiChat.suggest3'),
context.t('aiChat.suggest4'),
];
_initialized = true;
}
}
bool _initialized = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Row( title: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20), const Icon(Icons.auto_awesome_rounded, color: AppColors.primary, size: 20),
SizedBox(width: 8), const SizedBox(width: 8),
Text('AI 助手'), Text(context.t('aiChat.title')),
], ],
), ),
actions: [ actions: [
@ -79,7 +97,7 @@ class _AgentChatPageState extends State<AgentChatPage> {
child: TextField( child: TextField(
controller: _controller, controller: _controller,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '问我任何关于券的问题...', hintText: context.t('aiChat.inputHint'),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: AppSpacing.borderRadiusFull, borderRadius: AppSpacing.borderRadiusFull,
borderSide: const BorderSide(color: AppColors.borderLight), borderSide: const BorderSide(color: AppColors.borderLight),
@ -157,7 +175,7 @@ class _AgentChatPageState extends State<AgentChatPage> {
Future.delayed(const Duration(milliseconds: 800), () { Future.delayed(const Duration(milliseconds: 800), () {
if (mounted) { if (mounted) {
setState(() { setState(() {
_messages.add(_Msg(true, '根据您的偏好和消费习惯,推荐以下高性价比券:\n\n1. 星巴克 \$25 礼品卡 - 当前售价 \$21.258.5折信用AAA\n2. Amazon \$100 购物券 - 当前售价 \$858.5折信用AA\n\n这两张券的折扣率在同类中最优,且发行方信用等级高。')); _messages.add(_Msg(true, 'Based on your preferences, here are some great coupon recommendations:\n\n1. Starbucks \$25 Gift Card - \$21.25 (85% off), Credit AAA\n2. Amazon \$100 Voucher - \$85 (85% off), Credit AA\n\nThese coupons offer the best discount rates in their category with high issuer credit ratings.'));
}); });
} }
}); });

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -120,7 +121,7 @@ class AiChatPanel extends StatelessWidget {
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text('AI 助手', style: AppTypography.h3), Text(context.t('aiFab.title'), style: AppTypography.h3),
const Spacer(), const Spacer(),
IconButton( IconButton(
icon: const Icon(Icons.close_rounded, size: 22), icon: const Icon(Icons.close_rounded, size: 22),
@ -139,11 +140,9 @@ class AiChatPanel extends StatelessWidget {
controller: scrollController, controller: scrollController,
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
children: [ children: [
_buildAiMessage( _buildAiMessage(context.t('aiFab.greeting')),
'你好!我是 Genex AI 助手,可以帮你管理券资产、查找优惠、分析价格。有什么需要帮助的吗?',
),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildSuggestionChips(), _buildSuggestionChips(context),
], ],
), ),
), ),
@ -171,7 +170,7 @@ class AiChatPanel extends StatelessWidget {
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
hintText: '输入消息...', hintText: context.t('aiFab.inputHint'),
hintStyle: AppTypography.bodyMedium hintStyle: AppTypography.bodyMedium
.copyWith(color: AppColors.textTertiary), .copyWith(color: AppColors.textTertiary),
border: InputBorder.none, border: InputBorder.none,
@ -251,12 +250,12 @@ class AiChatPanel extends StatelessWidget {
); );
} }
Widget _buildSuggestionChips() { Widget _buildSuggestionChips(BuildContext context) {
final suggestions = [ final suggestions = [
'帮我找高折扣券', context.t('aiFab.suggest1'),
'我的券快到期了吗?', context.t('aiFab.suggest2'),
'推荐今日好券', context.t('aiFab.suggest3'),
'分析我的券资产', context.t('aiFab.suggest4'),
]; ];
return Wrap( return Wrap(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -36,7 +37,7 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(_step == 3 ? '' : '找回密码'), title: Text(_step == 3 ? '' : context.t('forgot.title')),
leading: _step == 3 leading: _step == 3
? const SizedBox.shrink() ? const SizedBox.shrink()
: IconButton( : IconButton(
@ -79,21 +80,21 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 24), const SizedBox(height: 24),
Text('输入手机号或邮箱', style: AppTypography.h1), Text(context.t('forgot.inputAccount'), style: AppTypography.h1),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('我们将向您发送验证码', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)), Text(context.t('forgot.sendHint'), style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)),
const SizedBox(height: 32), const SizedBox(height: 32),
TextField( TextField(
controller: _phoneController, controller: _phoneController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '手机号 / 邮箱地址', hintText: context.t('forgot.accountHint'),
prefixIcon: Icon(Icons.person_outline_rounded), prefixIcon: Icon(Icons.person_outline_rounded),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
GenexButton( GenexButton(
label: '获取验证码', label: context.t('forgot.getCode'),
onPressed: () => setState(() => _step = 1), onPressed: () => setState(() => _step = 1),
), ),
], ],
@ -105,18 +106,18 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 24), const SizedBox(height: 24),
Text('输入验证码', style: AppTypography.h1), Text(context.t('forgot.inputCode'), style: AppTypography.h1),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'验证码已发送至 ${_phoneController.text.isNotEmpty ? _phoneController.text : '***'}', '${context.t('forgot.codeSentTo')} ${_phoneController.text.isNotEmpty ? _phoneController.text : '***'}',
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary), style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
TextField( TextField(
controller: _codeController, controller: _codeController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '6位验证码', hintText: context.t('forgot.codeHint'),
prefixIcon: Icon(Icons.lock_outline_rounded), prefixIcon: Icon(Icons.lock_outline_rounded),
), ),
), ),
@ -125,12 +126,12 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: TextButton( child: TextButton(
onPressed: () {}, onPressed: () {},
child: const Text('重新发送'), child: Text(context.t('forgot.resend')),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
GenexButton( GenexButton(
label: '下一步', label: context.t('forgot.next'),
onPressed: () => setState(() => _step = 2), onPressed: () => setState(() => _step = 2),
), ),
], ],
@ -142,15 +143,15 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 24), const SizedBox(height: 24),
Text('设置新密码', style: AppTypography.h1), Text(context.t('forgot.setNewPassword'), style: AppTypography.h1),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('请输入新密码8位以上', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)), Text(context.t('forgot.newPasswordHint'), style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)),
const SizedBox(height: 32), const SizedBox(height: 32),
TextField( TextField(
controller: _passwordController, controller: _passwordController,
obscureText: _obscurePassword, obscureText: _obscurePassword,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '新密码', hintText: context.t('forgot.newPassword'),
prefixIcon: const Icon(Icons.lock_outline_rounded), prefixIcon: const Icon(Icons.lock_outline_rounded),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon(_obscurePassword ? Icons.visibility_off_outlined : Icons.visibility_outlined), icon: Icon(_obscurePassword ? Icons.visibility_off_outlined : Icons.visibility_outlined),
@ -163,7 +164,7 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
controller: _confirmController, controller: _confirmController,
obscureText: _obscureConfirm, obscureText: _obscureConfirm,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '确认新密码', hintText: context.t('forgot.confirmPassword'),
prefixIcon: const Icon(Icons.lock_outline_rounded), prefixIcon: const Icon(Icons.lock_outline_rounded),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon(_obscureConfirm ? Icons.visibility_off_outlined : Icons.visibility_outlined), icon: Icon(_obscureConfirm ? Icons.visibility_off_outlined : Icons.visibility_outlined),
@ -173,7 +174,7 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
GenexButton( GenexButton(
label: '确认修改', label: context.t('forgot.confirmChange'),
onPressed: () => setState(() => _step = 3), onPressed: () => setState(() => _step = 3),
), ),
], ],
@ -195,14 +196,14 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
child: const Icon(Icons.check_rounded, color: AppColors.success, size: 40), child: const Icon(Icons.check_rounded, color: AppColors.success, size: 40),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text('密码修改成功', style: AppTypography.h1), Text(context.t('forgot.success'), style: AppTypography.h1),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('请使用新密码登录', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)), Text(context.t('forgot.successHint'), style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)),
const SizedBox(height: 40), const SizedBox(height: 40),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: GenexButton( child: GenexButton(
label: '返回登录', label: context.t('forgot.backToLogin'),
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil('/login', (_) => false), onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil('/login', (_) => false),
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -50,10 +51,10 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 16), const SizedBox(height: 16),
Text('欢迎回来', style: AppTypography.displayMedium), Text(context.t('login.title'), style: AppTypography.displayMedium),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'登录 Genex 管理你的券资产', context.t('login.subtitle'),
style: AppTypography.bodyLarge.copyWith(color: AppColors.textSecondary), style: AppTypography.bodyLarge.copyWith(color: AppColors.textSecondary),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
@ -76,9 +77,9 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
labelColor: AppColors.textPrimary, labelColor: AppColors.textPrimary,
unselectedLabelColor: AppColors.textTertiary, unselectedLabelColor: AppColors.textTertiary,
labelStyle: AppTypography.labelMedium, labelStyle: AppTypography.labelMedium,
tabs: const [ tabs: [
Tab(text: '密码登录'), Tab(text: context.t('login.passwordTab')),
Tab(text: '验证码登录'), Tab(text: context.t('login.codeTab')),
], ],
), ),
), ),
@ -107,8 +108,8 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
TextField( TextField(
controller: _phoneController, controller: _phoneController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '手机号或邮箱', hintText: context.t('login.phoneOrEmail'),
prefixIcon: Icon(Icons.person_outline_rounded, color: AppColors.textTertiary), prefixIcon: Icon(Icons.person_outline_rounded, color: AppColors.textTertiary),
), ),
), ),
@ -119,7 +120,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
controller: _passwordController, controller: _passwordController,
obscureText: _obscurePassword, obscureText: _obscurePassword,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '密码', hintText: context.t('login.password'),
prefixIcon: const Icon(Icons.lock_outline_rounded, color: AppColors.textTertiary), prefixIcon: const Icon(Icons.lock_outline_rounded, color: AppColors.textTertiary),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
@ -140,7 +141,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
onTap: () { onTap: () {
Navigator.pushNamed(context, '/forgot-password'); Navigator.pushNamed(context, '/forgot-password');
}, },
child: Text('忘记密码?', style: AppTypography.labelSmall.copyWith( child: Text(context.t('login.forgotPassword'), style: AppTypography.labelSmall.copyWith(
color: AppColors.primary, color: AppColors.primary,
)), )),
), ),
@ -149,7 +150,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
// Login Button // Login Button
GenexButton( GenexButton(
label: '登录', label: context.t('login.submit'),
onPressed: () { onPressed: () {
Navigator.pushReplacementNamed(context, '/main'); Navigator.pushReplacementNamed(context, '/main');
}, },
@ -164,8 +165,8 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
// Phone Input // Phone Input
TextField( TextField(
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '手机号', hintText: context.t('login.phone'),
prefixIcon: Icon(Icons.phone_android_rounded, color: AppColors.textTertiary), prefixIcon: Icon(Icons.phone_android_rounded, color: AppColors.textTertiary),
), ),
), ),
@ -178,8 +179,8 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
child: TextField( child: TextField(
controller: _codeController, controller: _codeController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '验证码', hintText: context.t('login.verifyCode'),
prefixIcon: Icon(Icons.shield_outlined, color: AppColors.textTertiary), prefixIcon: Icon(Icons.shield_outlined, color: AppColors.textTertiary),
), ),
), ),
@ -188,7 +189,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
SizedBox( SizedBox(
height: AppSpacing.inputHeight, height: AppSpacing.inputHeight,
child: GenexButton( child: GenexButton(
label: '获取验证码', label: context.t('login.getCode'),
variant: GenexButtonVariant.secondary, variant: GenexButtonVariant.secondary,
size: GenexButtonSize.medium, size: GenexButtonSize.medium,
fullWidth: false, fullWidth: false,
@ -202,7 +203,7 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
const SizedBox(height: 24), const SizedBox(height: 24),
GenexButton( GenexButton(
label: '登录', label: context.t('login.submit'),
onPressed: () { onPressed: () {
Navigator.pushReplacementNamed(context, '/main'); Navigator.pushReplacementNamed(context, '/main');
}, },

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -48,10 +49,10 @@ class _RegisterPageState extends State<RegisterPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 16), const SizedBox(height: 16),
Text('创建账号', style: AppTypography.displayMedium), Text(context.t('register.title'), style: AppTypography.displayMedium),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
widget.isEmail ? '使用邮箱注册 Genex 账号' : '使用手机号注册 Genex 账号', widget.isEmail ? context.t('register.emailSubtitle') : context.t('register.phoneSubtitle'),
style: AppTypography.bodyLarge.copyWith(color: AppColors.textSecondary), style: AppTypography.bodyLarge.copyWith(color: AppColors.textSecondary),
), ),
const SizedBox(height: 40), const SizedBox(height: 40),
@ -62,7 +63,7 @@ class _RegisterPageState extends State<RegisterPage> {
// Account Input (Phone/Email) // Account Input (Phone/Email)
Text( Text(
widget.isEmail ? '邮箱地址' : '手机号', widget.isEmail ? context.t('register.email') : context.t('register.phone'),
style: AppTypography.labelMedium, style: AppTypography.labelMedium,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -71,7 +72,7 @@ class _RegisterPageState extends State<RegisterPage> {
keyboardType: keyboardType:
widget.isEmail ? TextInputType.emailAddress : TextInputType.phone, widget.isEmail ? TextInputType.emailAddress : TextInputType.phone,
decoration: InputDecoration( decoration: InputDecoration(
hintText: widget.isEmail ? '请输入邮箱地址' : '请输入手机号', hintText: widget.isEmail ? context.t('register.emailHint') : context.t('register.phoneHint'),
prefixIcon: Icon( prefixIcon: Icon(
widget.isEmail ? Icons.email_outlined : Icons.phone_android_rounded, widget.isEmail ? Icons.email_outlined : Icons.phone_android_rounded,
color: AppColors.textTertiary, color: AppColors.textTertiary,
@ -81,7 +82,7 @@ class _RegisterPageState extends State<RegisterPage> {
const SizedBox(height: 20), const SizedBox(height: 20),
// Verification Code // Verification Code
Text('验证码', style: AppTypography.labelMedium), Text(context.t('register.code'), style: AppTypography.labelMedium),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
children: [ children: [
@ -90,8 +91,8 @@ class _RegisterPageState extends State<RegisterPage> {
controller: _codeController, controller: _codeController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
maxLength: 6, maxLength: 6,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '请输入6位验证码', hintText: context.t('register.codeHint'),
counterText: '', counterText: '',
prefixIcon: Icon(Icons.shield_outlined, color: AppColors.textTertiary), prefixIcon: Icon(Icons.shield_outlined, color: AppColors.textTertiary),
), ),
@ -101,7 +102,7 @@ class _RegisterPageState extends State<RegisterPage> {
SizedBox( SizedBox(
height: AppSpacing.inputHeight, height: AppSpacing.inputHeight,
child: GenexButton( child: GenexButton(
label: '获取验证码', label: context.t('register.getCode'),
variant: GenexButtonVariant.secondary, variant: GenexButtonVariant.secondary,
size: GenexButtonSize.medium, size: GenexButtonSize.medium,
fullWidth: false, fullWidth: false,
@ -113,13 +114,13 @@ class _RegisterPageState extends State<RegisterPage> {
const SizedBox(height: 20), const SizedBox(height: 20),
// Password // Password
Text('设置密码', style: AppTypography.labelMedium), Text(context.t('register.setPassword'), style: AppTypography.labelMedium),
const SizedBox(height: 8), const SizedBox(height: 8),
TextField( TextField(
controller: _passwordController, controller: _passwordController,
obscureText: _obscurePassword, obscureText: _obscurePassword,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '8-20位含字母和数字', hintText: context.t('register.passwordHint'),
prefixIcon: const Icon(Icons.lock_outline_rounded, color: AppColors.textTertiary), prefixIcon: const Icon(Icons.lock_outline_rounded, color: AppColors.textTertiary),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
@ -159,14 +160,14 @@ class _RegisterPageState extends State<RegisterPage> {
text: TextSpan( text: TextSpan(
style: AppTypography.bodySmall, style: AppTypography.bodySmall,
children: [ children: [
const TextSpan(text: '我已阅读并同意 '), TextSpan(text: '${context.t('register.agreement')} '),
TextSpan( TextSpan(
text: '《用户协议》', text: context.t('register.userAgreement'),
style: AppTypography.bodySmall.copyWith(color: AppColors.primary), style: AppTypography.bodySmall.copyWith(color: AppColors.primary),
), ),
const TextSpan(text: ' '), const TextSpan(text: ' & '),
TextSpan( TextSpan(
text: '《隐私政策》', text: context.t('register.privacyPolicy'),
style: AppTypography.bodySmall.copyWith(color: AppColors.primary), style: AppTypography.bodySmall.copyWith(color: AppColors.primary),
), ),
], ],
@ -180,7 +181,7 @@ class _RegisterPageState extends State<RegisterPage> {
// Register Button // Register Button
GenexButton( GenexButton(
label: '注册', label: context.t('register.submit'),
onPressed: _agreeTerms ? () { onPressed: _agreeTerms ? () {
Navigator.pushReplacementNamed(context, '/main'); Navigator.pushReplacementNamed(context, '/main');
} : null, } : null,
@ -196,11 +197,11 @@ class _RegisterPageState extends State<RegisterPage> {
Widget _buildStepIndicator() { Widget _buildStepIndicator() {
return Row( return Row(
children: [ children: [
_buildStep(1, '验证', true), _buildStep(1, context.t('register.stepVerify'), true),
_buildStepLine(true), _buildStepLine(true),
_buildStep(2, '设密码', true), _buildStep(2, context.t('register.stepPassword'), true),
_buildStepLine(false), _buildStepLine(false),
_buildStep(3, '完成', false), _buildStep(3, context.t('register.stepDone'), false),
], ],
); );
} }
@ -255,11 +256,11 @@ class _RegisterPageState extends State<RegisterPage> {
return Row( return Row(
children: [ children: [
_buildCheck('8位以上', hasLength), _buildCheck(context.t('register.rule8chars'), hasLength),
const SizedBox(width: 16), const SizedBox(width: 16),
_buildCheck('含字母', hasLetter), _buildCheck(context.t('register.ruleLetter'), hasLetter),
const SizedBox(width: 16), const SizedBox(width: 16),
_buildCheck('含数字', hasDigit), _buildCheck(context.t('register.ruleNumber'), hasDigit),
], ],
); );
} }

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -49,7 +50,7 @@ class WelcomePage extends StatelessWidget {
// Slogan // Slogan
Text( Text(
'让每一张券都有价值', context.t('welcome.slogan'),
style: AppTypography.bodyLarge.copyWith( style: AppTypography.bodyLarge.copyWith(
color: AppColors.textSecondary, color: AppColors.textSecondary,
), ),
@ -59,7 +60,7 @@ class WelcomePage extends StatelessWidget {
// Phone Register // Phone Register
GenexButton( GenexButton(
label: '手机号注册', label: context.t('welcome.phoneRegister'),
icon: Icons.phone_android_rounded, icon: Icons.phone_android_rounded,
onPressed: () { onPressed: () {
Navigator.pushNamed(context, '/register'); Navigator.pushNamed(context, '/register');
@ -69,7 +70,7 @@ class WelcomePage extends StatelessWidget {
// Email Register // Email Register
GenexButton( GenexButton(
label: '邮箱注册', label: context.t('welcome.emailRegister'),
icon: Icons.email_outlined, icon: Icons.email_outlined,
variant: GenexButtonVariant.outline, variant: GenexButtonVariant.outline,
onPressed: () { onPressed: () {
@ -84,7 +85,7 @@ class WelcomePage extends StatelessWidget {
const Expanded(child: Divider(color: AppColors.border)), const Expanded(child: Divider(color: AppColors.border)),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text('其他方式登录', style: AppTypography.caption), child: Text(context.t('welcome.otherLogin'), style: AppTypography.caption),
), ),
const Expanded(child: Divider(color: AppColors.border)), const Expanded(child: Divider(color: AppColors.border)),
], ],
@ -118,14 +119,14 @@ class WelcomePage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text('已有账号?', style: AppTypography.bodyMedium.copyWith( Text(context.t('welcome.hasAccount'), style: AppTypography.bodyMedium.copyWith(
color: AppColors.textSecondary, color: AppColors.textSecondary,
)), )),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
Navigator.pushNamed(context, '/login'); Navigator.pushNamed(context, '/login');
}, },
child: Text('登录', style: AppTypography.labelMedium.copyWith( child: Text(context.t('welcome.login'), style: AppTypography.labelMedium.copyWith(
color: AppColors.primary, color: AppColors.primary,
)), )),
), ),
@ -135,7 +136,7 @@ class WelcomePage extends StatelessWidget {
// Terms // Terms
Text( Text(
'注册即表示同意《用户协议》和《隐私政策》', context.t('welcome.agreement'),
style: AppTypography.caption.copyWith(fontSize: 10), style: AppTypography.caption.copyWith(fontSize: 10),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -108,7 +109,7 @@ class CouponDetailPage extends StatelessWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'比面值节省 \$3.75', '${context.t('couponDetail.saveBadge')} \$3.75',
style: AppTypography.bodySmall.copyWith(color: AppColors.success), style: AppTypography.bodySmall.copyWith(color: AppColors.success),
), ),
], ],
@ -119,31 +120,31 @@ class CouponDetailPage extends StatelessWidget {
// Info Cards // Info Cards
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0), padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
child: _buildInfoSection(), child: _buildInfoSection(context),
), ),
// Usage Rules // Usage Rules
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0), padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
child: _buildUsageRules(), child: _buildUsageRules(context),
), ),
// Available Stores // Available Stores
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0), padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
child: _buildStores(), child: _buildStores(context),
), ),
// Price Trend (Optional) // Price Trend (Optional)
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0), padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
child: _buildPriceTrend(), child: _buildPriceTrend(context),
), ),
// Similar Coupons // Similar Coupons
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
child: Text('同类券推荐', style: AppTypography.h3), child: Text(context.t('couponDetail.similar'), style: AppTypography.h3),
), ),
SizedBox( SizedBox(
height: 180, height: 180,
@ -177,7 +178,7 @@ class CouponDetailPage extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.favorite_border_rounded, color: AppColors.textTertiary, size: 22), const Icon(Icons.favorite_border_rounded, color: AppColors.textTertiary, size: 22),
Text('收藏', style: AppTypography.caption), Text(context.t('couponDetail.favorite'), style: AppTypography.caption),
], ],
), ),
const SizedBox(width: 24), const SizedBox(width: 24),
@ -185,7 +186,7 @@ class CouponDetailPage extends StatelessWidget {
// Buy Button // Buy Button
Expanded( Expanded(
child: GenexButton( child: GenexButton(
label: '立即购买 \$21.25', label: '${context.t('couponDetail.buyNow')} \$21.25',
onPressed: () { onPressed: () {
Navigator.pushNamed(context, '/order/confirm'); Navigator.pushNamed(context, '/order/confirm');
}, },
@ -211,12 +212,12 @@ class CouponDetailPage extends StatelessWidget {
); );
} }
Widget _buildInfoSection() { Widget _buildInfoSection(BuildContext context) {
final items = [ final items = [
('面值', '\$25.00'), (context.t('couponDetail.faceValue'), '\$25.00'),
('有效期', '2026/12/31'), (context.t('couponDetail.validUntil'), '2026/12/31'),
('类型', '消费券'), (context.t('couponDetail.type'), context.t('couponDetail.consumeCoupon')),
('发行方', 'Starbucks Inc.'), (context.t('couponDetail.issuer'), 'Starbucks Inc.'),
]; ];
return Container( return Container(
@ -251,7 +252,7 @@ class CouponDetailPage extends StatelessWidget {
); );
} }
Widget _buildUsageRules() { Widget _buildUsageRules(BuildContext context) {
return Container( return Container(
padding: AppSpacing.cardPadding, padding: AppSpacing.cardPadding,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -262,13 +263,13 @@ class CouponDetailPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('使用说明', style: AppTypography.labelMedium), Text(context.t('couponDetail.usageNote'), style: AppTypography.labelMedium),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildRuleItem(Icons.check_circle_outline, '全国星巴克门店通用'), _buildRuleItem(Icons.check_circle_outline, context.t('couponDetail.allStores')),
_buildRuleItem(Icons.check_circle_outline, '可转赠给好友'), _buildRuleItem(Icons.check_circle_outline, context.t('couponDetail.canTransfer')),
_buildRuleItem(Icons.check_circle_outline, '有效期内随时使用'), _buildRuleItem(Icons.check_circle_outline, context.t('couponDetail.useAnytime')),
_buildRuleItem(Icons.info_outline_rounded, '不可叠加使用'), _buildRuleItem(Icons.info_outline_rounded, context.t('couponDetail.noStack')),
_buildRuleItem(Icons.info_outline_rounded, '不可兑换现金'), _buildRuleItem(Icons.info_outline_rounded, context.t('couponDetail.noCash')),
], ],
), ),
); );
@ -288,7 +289,7 @@ class CouponDetailPage extends StatelessWidget {
); );
} }
Widget _buildStores() { Widget _buildStores(BuildContext context) {
return Container( return Container(
padding: AppSpacing.cardPadding, padding: AppSpacing.cardPadding,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -302,15 +303,15 @@ class CouponDetailPage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('使用门店', style: AppTypography.labelMedium), Text(context.t('couponDetail.stores'), style: AppTypography.labelMedium),
Text('全国 12,800+ 门店', style: AppTypography.caption.copyWith( Text(context.t('couponDetail.storeCount'), style: AppTypography.caption.copyWith(
color: AppColors.primary, color: AppColors.primary,
)), )),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
'支持全国所有星巴克直营门店使用', context.t('couponDetail.storeDesc'),
style: AppTypography.bodySmall, style: AppTypography.bodySmall,
), ),
], ],
@ -318,7 +319,7 @@ class CouponDetailPage extends StatelessWidget {
); );
} }
Widget _buildPriceTrend() { Widget _buildPriceTrend(BuildContext context) {
return Container( return Container(
padding: AppSpacing.cardPadding, padding: AppSpacing.cardPadding,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -332,8 +333,8 @@ class CouponDetailPage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('价格走势', style: AppTypography.labelMedium), Text(context.t('couponDetail.priceTrend'), style: AppTypography.labelMedium),
Text('近30天', style: AppTypography.caption), Text(context.t('couponDetail.last30Days'), style: AppTypography.caption),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -346,7 +347,7 @@ class CouponDetailPage extends StatelessWidget {
), ),
child: Center( child: Center(
child: Text( child: Text(
'价格走势图 (fl_chart)', '${context.t('couponDetail.priceTrend')} (fl_chart)',
style: AppTypography.bodySmall.copyWith(color: AppColors.textTertiary), style: AppTypography.bodySmall.copyWith(color: AppColors.textTertiary),
), ),
), ),
@ -355,10 +356,10 @@ class CouponDetailPage extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildTrendStat('最高', '\$22.50', AppColors.error), _buildTrendStat(context.t('couponDetail.highest'), '\$22.50', AppColors.error),
_buildTrendStat('最低', '\$20.00', AppColors.success), _buildTrendStat(context.t('couponDetail.lowest'), '\$20.00', AppColors.success),
_buildTrendStat('均价', '\$21.10', AppColors.textSecondary), _buildTrendStat(context.t('couponDetail.average'), '\$21.10', AppColors.textSecondary),
_buildTrendStat('历史成交', '1,234笔', AppColors.primary), _buildTrendStat(context.t('couponDetail.tradeHistory'), '1,234', AppColors.primary),
], ],
), ),
], ],
@ -403,7 +404,7 @@ class CouponDetailPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('品牌 ${index + 1}', style: AppTypography.caption), Text('Brand ${index + 1}', style: AppTypography.caption),
const SizedBox(height: 2), const SizedBox(height: 2),
Text('\$${(index + 1) * 8}.50', Text('\$${(index + 1) * 8}.50',
style: AppTypography.priceSmall.copyWith(fontSize: 14)), style: AppTypography.priceSmall.copyWith(fontSize: 14)),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -36,13 +37,13 @@ class HomePage extends StatelessWidget {
), ),
// Banner Carousel // Banner Carousel
SliverToBoxAdapter(child: _buildBanner()), SliverToBoxAdapter(child: _buildBanner(context)),
// Category Grid // Category Grid
SliverToBoxAdapter(child: _buildCategoryGrid()), SliverToBoxAdapter(child: _buildCategoryGrid(context)),
// AI Smart Suggestions // AI Smart Suggestions
SliverToBoxAdapter(child: _buildAiSuggestions()), SliverToBoxAdapter(child: _buildAiSuggestions(context)),
// Section: Featured Coupons // Section: Featured Coupons
SliverToBoxAdapter( SliverToBoxAdapter(
@ -51,10 +52,10 @@ class HomePage extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('精选好券', style: AppTypography.h2), Text(context.t('home.featuredCoupons'), style: AppTypography.h2),
GestureDetector( GestureDetector(
onTap: () {}, onTap: () {},
child: Text('查看全部', style: AppTypography.labelSmall.copyWith( child: Text(context.t('home.viewAllCoupons'), style: AppTypography.labelSmall.copyWith(
color: AppColors.primary, color: AppColors.primary,
)), )),
), ),
@ -125,7 +126,7 @@ class HomePage extends StatelessWidget {
const Icon(Icons.search_rounded, size: 20, color: AppColors.textTertiary), const Icon(Icons.search_rounded, size: 20, color: AppColors.textTertiary),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'搜索券、品牌、分类...', context.t('home.searchHint'),
style: AppTypography.bodyMedium.copyWith(color: AppColors.textTertiary), style: AppTypography.bodyMedium.copyWith(color: AppColors.textTertiary),
), ),
], ],
@ -134,7 +135,7 @@ class HomePage extends StatelessWidget {
); );
} }
Widget _buildBanner() { Widget _buildBanner(BuildContext context) {
return Container( return Container(
height: 160, height: 160,
margin: const EdgeInsets.fromLTRB(20, 8, 20, 0), margin: const EdgeInsets.fromLTRB(20, 8, 20, 0),
@ -146,8 +147,8 @@ class HomePage extends StatelessWidget {
AppColors.successGradient, AppColors.successGradient,
AppColors.cardGradient, AppColors.cardGradient,
]; ];
final titles = ['新用户专享', '限时折扣', '热门推荐']; final titleKeys = ['home.bannerNewUser', 'home.bannerDiscount', 'home.bannerHot'];
final subtitles = ['首单立减 \$10', '全场低至7折', '精选高折扣券']; final subtitleKeys = ['home.bannerNewUserDesc', 'home.bannerDiscountDesc', 'home.bannerHotDesc'];
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 4), margin: const EdgeInsets.symmetric(horizontal: 4),
@ -161,12 +162,12 @@ class HomePage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Text( Text(
titles[index], context.t(titleKeys[index]),
style: AppTypography.h1.copyWith(color: Colors.white), style: AppTypography.h1.copyWith(color: Colors.white),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
subtitles[index], context.t(subtitleKeys[index]),
style: AppTypography.bodyMedium.copyWith( style: AppTypography.bodyMedium.copyWith(
color: Colors.white.withValues(alpha: 0.8), color: Colors.white.withValues(alpha: 0.8),
), ),
@ -179,16 +180,16 @@ class HomePage extends StatelessWidget {
); );
} }
Widget _buildCategoryGrid() { Widget _buildCategoryGrid(BuildContext context) {
final categories = [ final categoryKeys = [
('餐饮', Icons.restaurant_rounded, AppColors.couponDining), ('home.dining', Icons.restaurant_rounded, AppColors.couponDining),
('购物', Icons.shopping_bag_rounded, AppColors.couponShopping), ('home.shopping', Icons.shopping_bag_rounded, AppColors.couponShopping),
('娱乐', Icons.sports_esports_rounded, AppColors.couponEntertainment), ('home.entertainment', Icons.sports_esports_rounded, AppColors.couponEntertainment),
('出行', Icons.directions_car_rounded, AppColors.couponTravel), ('home.travel', Icons.directions_car_rounded, AppColors.couponTravel),
('生活', Icons.home_rounded, AppColors.couponOther), ('home.lifestyle', Icons.home_rounded, AppColors.couponOther),
('品牌', Icons.storefront_rounded, AppColors.primary), ('home.brand', Icons.storefront_rounded, AppColors.primary),
('折扣', Icons.local_offer_rounded, AppColors.error), ('home.discount', Icons.local_offer_rounded, AppColors.error),
('全部', Icons.grid_view_rounded, AppColors.textSecondary), ('home.allCategories', Icons.grid_view_rounded, AppColors.textSecondary),
]; ];
return Padding( return Padding(
@ -202,9 +203,9 @@ class HomePage extends StatelessWidget {
crossAxisSpacing: 8, crossAxisSpacing: 8,
childAspectRatio: 0.85, childAspectRatio: 0.85,
), ),
itemCount: categories.length, itemCount: categoryKeys.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final (name, icon, color) = categories[index]; final (key, icon, color) = categoryKeys[index];
return GestureDetector( return GestureDetector(
onTap: () {}, onTap: () {},
child: Column( child: Column(
@ -220,7 +221,7 @@ class HomePage extends StatelessWidget {
child: Icon(icon, color: color, size: 24), child: Icon(icon, color: color, size: 24),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text(name, style: AppTypography.caption.copyWith( Text(context.t(key), style: AppTypography.caption.copyWith(
color: AppColors.textPrimary, color: AppColors.textPrimary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
)), )),
@ -232,7 +233,7 @@ class HomePage extends StatelessWidget {
); );
} }
Widget _buildAiSuggestions() { Widget _buildAiSuggestions(BuildContext context) {
return Container( return Container(
margin: const EdgeInsets.fromLTRB(20, 16, 20, 0), margin: const EdgeInsets.fromLTRB(20, 16, 20, 0),
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
@ -257,12 +258,12 @@ class HomePage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('AI 推荐', style: AppTypography.labelSmall.copyWith( Text(context.t('home.aiRecommend'), style: AppTypography.labelSmall.copyWith(
color: AppColors.primary, color: AppColors.primary,
)), )),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
'根据你的偏好发现了3张高性价比券', context.t('home.aiRecommendDesc'),
style: AppTypography.bodySmall, style: AppTypography.bodySmall,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -35,7 +36,7 @@ class _MarketPageState extends State<MarketPage> with SingleTickerProviderStateM
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('交易市场'), title: Text(context.t('market.title')),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(AppSpacing.tabBarHeight), preferredSize: const Size.fromHeight(AppSpacing.tabBarHeight),
child: Container( child: Container(
@ -55,9 +56,9 @@ class _MarketPageState extends State<MarketPage> with SingleTickerProviderStateM
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
labelColor: AppColors.textPrimary, labelColor: AppColors.textPrimary,
unselectedLabelColor: AppColors.textTertiary, unselectedLabelColor: AppColors.textTertiary,
tabs: const [ tabs: [
Tab(text: '一级市场(全新)'), Tab(text: context.t('market.primary')),
Tab(text: '二级市场(转售)'), Tab(text: context.t('market.secondary')),
], ],
), ),
), ),
@ -90,17 +91,24 @@ class _MarketPageState extends State<MarketPage> with SingleTickerProviderStateM
} }
Widget _buildFilters() { Widget _buildFilters() {
final categories = ['全部', '餐饮', '购物', '娱乐', '出行', '生活']; final categoryKeys = [
'common.all',
'market.dining',
'market.shopping',
'market.entertainment',
'market.travel',
'market.lifestyle',
];
return SizedBox( return SizedBox(
height: 36, height: 36,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
itemCount: categories.length, itemCount: categoryKeys.length,
separatorBuilder: (_, __) => const SizedBox(width: 8), separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final cat = categories[index]; final cat = context.t(categoryKeys[index]);
final isSelected = _selectedCategory == cat || (index == 0 && _selectedCategory == null); final isSelected = _selectedCategory == cat || (index == 0 && _selectedCategory == null);
return GestureDetector( return GestureDetector(
onTap: () => setState(() => _selectedCategory = index == 0 ? null : cat), onTap: () => setState(() => _selectedCategory = index == 0 ? null : cat),
@ -127,10 +135,10 @@ class _MarketPageState extends State<MarketPage> with SingleTickerProviderStateM
Widget _buildSortBar() { Widget _buildSortBar() {
final sortOptions = [ final sortOptions = [
('discount', '折扣率'), ('discount', context.t('market.discountRate')),
('price_asc', '价格↑'), ('price_asc', context.t('market.priceUp')),
('price_desc', '价格↓'), ('price_desc', context.t('market.priceDown')),
('expiry', '到期时间'), ('expiry', context.t('market.expiryDate')),
]; ];
return Padding( return Padding(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -20,7 +21,7 @@ class MyCouponDetailPage extends StatelessWidget {
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20), icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
title: const Text('券详情'), title: Text(context.t('myCoupon.title')),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.more_horiz_rounded), icon: const Icon(Icons.more_horiz_rounded),
@ -66,7 +67,7 @@ class MyCouponDetailPage extends StatelessWidget {
color: Colors.white24, color: Colors.white24,
borderRadius: AppSpacing.borderRadiusFull, borderRadius: AppSpacing.borderRadiusFull,
), ),
child: Text('可使用', style: AppTypography.caption.copyWith( child: Text(context.t('myCoupon.active'), style: AppTypography.caption.copyWith(
color: Colors.white, color: Colors.white,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
)), )),
@ -101,7 +102,7 @@ class MyCouponDetailPage extends StatelessWidget {
// Instructions // Instructions
Text( Text(
'出示此二维码给商户扫描核销', context.t('myCoupon.showQrHint'),
style: AppTypography.bodySmall.copyWith(color: Colors.white70), style: AppTypography.bodySmall.copyWith(color: Colors.white70),
), ),
@ -112,7 +113,7 @@ class MyCouponDetailPage extends StatelessWidget {
onPressed: () {}, onPressed: () {},
icon: const Icon(Icons.view_headline_rounded, size: 18, icon: const Icon(Icons.view_headline_rounded, size: 18,
color: Colors.white70), color: Colors.white70),
label: Text('切换条形码', style: AppTypography.labelSmall.copyWith( label: Text(context.t('myCoupon.switchBarcode'), style: AppTypography.labelSmall.copyWith(
color: Colors.white70, color: Colors.white70,
)), )),
), ),
@ -131,15 +132,15 @@ class MyCouponDetailPage extends StatelessWidget {
), ),
child: Column( child: Column(
children: [ children: [
_infoRow('面值', '\$25.00'), _infoRow(context.t('myCoupon.faceValue'), '\$25.00'),
const Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Divider()), const Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Divider()),
_infoRow('购买价格', '\$21.25'), _infoRow(context.t('myCoupon.purchasePrice'), '\$21.25'),
const Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Divider()), const Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Divider()),
_infoRow('有效期', '2026/12/31'), _infoRow(context.t('myCoupon.validUntil'), '2026/12/31'),
const Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Divider()), const Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Divider()),
_infoRow('订单号', 'GNX-20260209-001234'), _infoRow(context.t('myCoupon.orderNo'), 'GNX-20260209-001234'),
const Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Divider()), const Padding(padding: EdgeInsets.symmetric(vertical: 10), child: Divider()),
_infoRow('剩余可转售次数', '3次'), _infoRow(context.t('myCoupon.resellCount'), '3'),
], ],
), ),
), ),
@ -150,7 +151,7 @@ class MyCouponDetailPage extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: GenexButton( child: GenexButton(
label: '转赠', label: context.t('myCoupon.transfer'),
icon: Icons.card_giftcard_rounded, icon: Icons.card_giftcard_rounded,
variant: GenexButtonVariant.secondary, variant: GenexButtonVariant.secondary,
onPressed: () { onPressed: () {
@ -161,7 +162,7 @@ class MyCouponDetailPage extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: GenexButton( child: GenexButton(
label: '出售', label: context.t('myCoupon.sell'),
icon: Icons.sell_rounded, icon: Icons.sell_rounded,
variant: GenexButtonVariant.outline, variant: GenexButtonVariant.outline,
onPressed: () { onPressed: () {
@ -185,12 +186,12 @@ class MyCouponDetailPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('使用说明', style: AppTypography.labelMedium), Text(context.t('myCoupon.usageNote'), style: AppTypography.labelMedium),
const SizedBox(height: 12), const SizedBox(height: 12),
_ruleItem('全国星巴克门店通用'), _ruleItem(context.t('myCoupon.useInStore')),
_ruleItem('请在有效期内使用'), _ruleItem(context.t('myCoupon.useInTime')),
_ruleItem('每次消费仅可使用一张'), _ruleItem(context.t('myCoupon.onePerVisit')),
_ruleItem('不可兑换现金'), _ruleItem(context.t('myCoupon.noCash')),
], ],
), ),
), ),
@ -249,11 +250,11 @@ class MyCouponDetailPage extends StatelessWidget {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_optionTile(Icons.wallet_rounded, '提取到外部钱包', '需KYC L2+认证', () {}), _optionTile(Icons.wallet_rounded, context.t('myCoupon.extractToWallet'), context.t('myCoupon.requireKycL2'), () {}),
const Divider(), const Divider(),
_optionTile(Icons.receipt_long_rounded, '查看交易记录', '', () {}), _optionTile(Icons.receipt_long_rounded, context.t('myCoupon.viewTrades'), '', () {}),
const Divider(), const Divider(),
_optionTile(Icons.help_outline_rounded, '使用帮助', '', () {}), _optionTile(Icons.help_outline_rounded, context.t('myCoupon.help'), '', () {}),
], ],
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -37,7 +38,7 @@ class _MyCouponsPageState extends State<MyCouponsPage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('我的券'), title: Text(context.t('myCoupons.title')),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.sort_rounded, size: 22), icon: const Icon(Icons.sort_rounded, size: 22),
@ -46,11 +47,11 @@ class _MyCouponsPageState extends State<MyCouponsPage>
], ],
bottom: TabBar( bottom: TabBar(
controller: _tabController, controller: _tabController,
tabs: const [ tabs: [
Tab(text: '全部'), Tab(text: context.t('common.all')),
Tab(text: '可使用'), Tab(text: context.t('myCoupons.usable')),
Tab(text: '待核销'), Tab(text: context.t('myCoupons.pendingRedeem')),
Tab(text: '已过期'), Tab(text: context.t('myCoupons.expired')),
], ],
), ),
), ),
@ -70,6 +71,7 @@ class _MyCouponsPageState extends State<MyCouponsPage>
// Example: show empty state for expired tab // Example: show empty state for expired tab
if (filter == CouponStatus.expired) { if (filter == CouponStatus.expired) {
return EmptyState.noCoupons( return EmptyState.noCoupons(
context: context,
onBrowse: () { onBrowse: () {
// noop - market tab accessible from bottom nav // noop - market tab accessible from bottom nav
}, },
@ -160,10 +162,10 @@ class _MyCouponCard extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
children: [ children: [
Text('面值 \$${faceValue.toStringAsFixed(0)}', Text('${context.t('myCoupons.faceValue')} \$${faceValue.toStringAsFixed(0)}',
style: AppTypography.bodySmall), style: AppTypography.bodySmall),
const SizedBox(width: 8), const SizedBox(width: 8),
_statusWidget, _buildStatusWidget(context),
], ],
), ),
], ],
@ -187,14 +189,14 @@ class _MyCouponCard extends StatelessWidget {
Icon(Icons.access_time_rounded, size: 14, color: _expiryColor), Icon(Icons.access_time_rounded, size: 14, color: _expiryColor),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
_expiryText, _getExpiryText(context),
style: AppTypography.caption.copyWith(color: _expiryColor), style: AppTypography.caption.copyWith(color: _expiryColor),
), ),
const Spacer(), const Spacer(),
if (status == CouponStatus.active) ...[ if (status == CouponStatus.active) ...[
_quickAction('转赠', Icons.card_giftcard_rounded), _quickAction(context.t('myCoupons.transfer'), Icons.card_giftcard_rounded),
const SizedBox(width: 12), const SizedBox(width: 12),
_quickAction('出售', Icons.sell_rounded), _quickAction(context.t('myCoupons.sell'), Icons.sell_rounded),
], ],
], ],
), ),
@ -205,16 +207,16 @@ class _MyCouponCard extends StatelessWidget {
); );
} }
Widget get _statusWidget { Widget _buildStatusWidget(BuildContext context) {
switch (status) { switch (status) {
case CouponStatus.active: case CouponStatus.active:
return StatusTags.active(); return StatusTags.active(context: context);
case CouponStatus.pending: case CouponStatus.pending:
return StatusTags.pending(); return StatusTags.pending(context: context);
case CouponStatus.expired: case CouponStatus.expired:
return StatusTags.expired(); return StatusTags.expired(context: context);
case CouponStatus.used: case CouponStatus.used:
return StatusTags.used(); return StatusTags.used(context: context);
} }
} }
@ -232,12 +234,12 @@ class _MyCouponCard extends StatelessWidget {
); );
} }
String get _expiryText { String _getExpiryText(BuildContext context) {
final days = expiryDate.difference(DateTime.now()).inDays; final days = expiryDate.difference(DateTime.now()).inDays;
if (days < 0) return '已过期'; if (days < 0) return context.t('myCoupons.expiredText');
if (days == 0) return '今天到期'; if (days == 0) return context.t('myCoupons.expiringToday');
if (days <= 7) return '$days天后到期'; if (days <= 7) return '$days${context.t('myCoupons.daysToExpiry')}';
return '${expiryDate.year}/${expiryDate.month}/${expiryDate.day}到期'; return '${expiryDate.year}/${expiryDate.month}/${expiryDate.day}${context.t('couponCard.expiryFormat')}';
} }
Color get _expiryColor { Color get _expiryColor {

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -30,7 +31,7 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20), icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
title: const Text('确认订单'), title: Text(context.t('orderConfirm.title')),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: AppSpacing.pagePadding, padding: AppSpacing.pagePadding,
@ -69,7 +70,7 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'您正在购买消费券用于消费', context.t('orderConfirm.buyingNote'),
style: AppTypography.bodySmall.copyWith( style: AppTypography.bodySmall.copyWith(
color: AppColors.gray700, color: AppColors.gray700,
), ),
@ -97,7 +98,7 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('合计', style: AppTypography.caption), Text(context.t('orderConfirm.total'), style: AppTypography.caption),
Text( Text(
'\$${_totalPrice.toStringAsFixed(2)}', '\$${_totalPrice.toStringAsFixed(2)}',
style: AppTypography.priceMedium, style: AppTypography.priceMedium,
@ -107,7 +108,7 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
const SizedBox(width: 20), const SizedBox(width: 20),
Expanded( Expanded(
child: GenexButton( child: GenexButton(
label: '确认支付', label: context.t('orderConfirm.confirmPay'),
onPressed: () => _showPaymentAuth(context), onPressed: () => _showPaymentAuth(context),
), ),
), ),
@ -183,7 +184,7 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('购买数量', style: AppTypography.labelMedium), Text(context.t('orderConfirm.quantity'), style: AppTypography.labelMedium),
Row( Row(
children: [ children: [
_buildQtyButton(Icons.remove_rounded, () { _buildQtyButton(Icons.remove_rounded, () {
@ -221,7 +222,7 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
Widget _buildPaymentMethods() { Widget _buildPaymentMethods() {
final methods = [ final methods = [
('银行卡/信用卡', Icons.credit_card_rounded), (context.t('orderConfirm.bankCard'), Icons.credit_card_rounded),
('Apple Pay', Icons.apple_rounded), ('Apple Pay', Icons.apple_rounded),
]; ];
@ -235,7 +236,7 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('支付方式', style: AppTypography.labelMedium), Text(context.t('orderConfirm.paymentMethod'), style: AppTypography.labelMedium),
const SizedBox(height: 12), const SizedBox(height: 12),
...methods.asMap().entries.map((entry) { ...methods.asMap().entries.map((entry) {
final isSelected = _selectedPayment == entry.key; final isSelected = _selectedPayment == entry.key;
@ -285,15 +286,15 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
), ),
child: Column( child: Column(
children: [ children: [
_priceRow('单价', '\$${_unitPrice.toStringAsFixed(2)}'), _priceRow(context.t('orderConfirm.unitPrice'), '\$${_unitPrice.toStringAsFixed(2)}'),
const SizedBox(height: 8), const SizedBox(height: 8),
_priceRow('数量', '×$_quantity'), _priceRow(context.t('orderConfirm.count'), '$_quantity'),
const Padding( const Padding(
padding: EdgeInsets.symmetric(vertical: 10), padding: EdgeInsets.symmetric(vertical: 10),
child: Divider(), child: Divider(),
), ),
_priceRow( _priceRow(
'合计', context.t('orderConfirm.total'),
'\$${_totalPrice.toStringAsFixed(2)}', '\$${_totalPrice.toStringAsFixed(2)}',
valueStyle: AppTypography.priceMedium, valueStyle: AppTypography.priceMedium,
), ),
@ -301,7 +302,7 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Text( child: Text(
'比面值节省 \$${(25.0 * _quantity - _totalPrice).toStringAsFixed(2)}', '${context.t('orderConfirm.saveBadge')} \$${(25.0 * _quantity - _totalPrice).toStringAsFixed(2)}',
style: AppTypography.caption.copyWith(color: AppColors.success), style: AppTypography.caption.copyWith(color: AppColors.success),
), ),
), ),
@ -337,14 +338,14 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text('确认支付', style: AppTypography.h2), Text(context.t('orderConfirm.confirmPay'), style: AppTypography.h2),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'\$${_totalPrice.toStringAsFixed(2)}', '\$${_totalPrice.toStringAsFixed(2)}',
style: AppTypography.priceLarge.copyWith(fontSize: 36), style: AppTypography.priceLarge.copyWith(fontSize: 36),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('星巴克 \$25 礼品卡 × $_quantity', Text('星巴克 \$25 礼品卡 x $_quantity',
style: AppTypography.bodySmall), style: AppTypography.bodySmall),
const SizedBox(height: 32), const SizedBox(height: 32),
// Biometric / Password // Biometric / Password
@ -358,10 +359,10 @@ class _OrderConfirmPageState extends State<OrderConfirmPage> {
size: 36, color: AppColors.primary), size: 36, color: AppColors.primary),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Text('请验证指纹或面容以完成支付', style: AppTypography.bodySmall), Text(context.t('orderConfirm.biometricHint'), style: AppTypography.bodySmall),
const SizedBox(height: 24), const SizedBox(height: 24),
GenexButton( GenexButton(
label: '使用密码支付', label: context.t('orderConfirm.usePasswordPay'),
variant: GenexButtonVariant.text, variant: GenexButtonVariant.text,
onPressed: () { onPressed: () {
Navigator.pop(ctx); Navigator.pop(ctx);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -17,17 +18,18 @@ class PaymentPage extends StatefulWidget {
class _PaymentPageState extends State<PaymentPage> { class _PaymentPageState extends State<PaymentPage> {
int _selectedMethod = 0; int _selectedMethod = 0;
final _methods = const [ List<_PaymentMethod> _getMethods(BuildContext context) => [
_PaymentMethod('Visa •••• 4242', Icons.credit_card_rounded, 'visa'), _PaymentMethod('Visa •••• 4242', Icons.credit_card_rounded, 'visa'),
_PaymentMethod('Apple Pay', Icons.apple_rounded, 'apple_pay'), _PaymentMethod('Apple Pay', Icons.apple_rounded, 'apple_pay'),
_PaymentMethod('Google Pay', Icons.account_balance_wallet_rounded, 'google_pay'), _PaymentMethod('Google Pay', Icons.account_balance_wallet_rounded, 'google_pay'),
_PaymentMethod('银行转账', Icons.account_balance_rounded, 'bank'), _PaymentMethod(context.t('payment.bankTransfer'), Icons.account_balance_rounded, 'bank'),
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final methods = _getMethods(context);
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('选择支付方式')), appBar: AppBar(title: Text(context.t('payment.title'))),
body: Column( body: Column(
children: [ children: [
// Order Summary // Order Summary
@ -57,7 +59,7 @@ class _PaymentPageState extends State<PaymentPage> {
children: [ children: [
Text('星巴克 \$25 礼品卡', style: AppTypography.labelMedium), Text('星巴克 \$25 礼品卡', style: AppTypography.labelMedium),
const SizedBox(height: 2), const SizedBox(height: 2),
Text('面值 \$25.00', style: AppTypography.bodySmall), Text('${context.t('couponDetail.faceValue')} \$25.00', style: AppTypography.bodySmall),
], ],
), ),
), ),
@ -70,9 +72,9 @@ class _PaymentPageState extends State<PaymentPage> {
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
itemCount: _methods.length, itemCount: methods.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final method = _methods[index]; final method = methods[index];
final isSelected = _selectedMethod == index; final isSelected = _selectedMethod == index;
return GestureDetector( return GestureDetector(
onTap: () => setState(() => _selectedMethod = index), onTap: () => setState(() => _selectedMethod = index),
@ -110,7 +112,7 @@ class _PaymentPageState extends State<PaymentPage> {
child: TextButton.icon( child: TextButton.icon(
onPressed: () {}, onPressed: () {},
icon: const Icon(Icons.add_rounded), icon: const Icon(Icons.add_rounded),
label: const Text('添加新支付方式'), label: Text(context.t('payment.addNew')),
), ),
), ),
@ -122,10 +124,9 @@ class _PaymentPageState extends State<PaymentPage> {
height: AppSpacing.buttonHeight, height: AppSpacing.buttonHeight,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
//
Navigator.pushNamed(context, '/payment/success'); Navigator.pushNamed(context, '/payment/success');
}, },
child: const Text('确认支付 \$21.25'), child: Text('${context.t('payment.confirmPay')} \$21.25'),
), ),
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -45,10 +46,10 @@ class PaymentSuccessPage extends StatelessWidget {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text('支付成功', style: AppTypography.h1), Text(context.t('paymentSuccess.title'), style: AppTypography.h1),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'券已到账,可在「我的券」中查看', context.t('paymentSuccess.hint'),
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary), style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
@ -64,19 +65,19 @@ class PaymentSuccessPage extends StatelessWidget {
), ),
child: Column( child: Column(
children: [ children: [
_infoRow('券名称', couponName), _infoRow(context.t('paymentSuccess.couponName'), couponName),
const SizedBox(height: 10), const SizedBox(height: 10),
const Divider(), const Divider(),
const SizedBox(height: 10), const SizedBox(height: 10),
_infoRow('支付金额', '\$$amount'), _infoRow(context.t('paymentSuccess.payAmount'), '\$$amount'),
const SizedBox(height: 10), const SizedBox(height: 10),
const Divider(), const Divider(),
const SizedBox(height: 10), const SizedBox(height: 10),
_infoRow('订单号', orderNumber), _infoRow(context.t('paymentSuccess.orderNo'), orderNumber),
const SizedBox(height: 10), const SizedBox(height: 10),
const Divider(), const Divider(),
const SizedBox(height: 10), const SizedBox(height: 10),
_infoRow('支付时间', '2026-02-09 14:32:15'), _infoRow(context.t('paymentSuccess.payTime'), '2026-02-09 14:32:15'),
], ],
), ),
), ),
@ -85,14 +86,14 @@ class PaymentSuccessPage extends StatelessWidget {
// Actions // Actions
GenexButton( GenexButton(
label: '查看我的券', label: context.t('paymentSuccess.viewMyCoupon'),
onPressed: () { onPressed: () {
Navigator.pushNamedAndRemoveUntil(context, '/main', (route) => false); Navigator.pushNamedAndRemoveUntil(context, '/main', (route) => false);
}, },
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
GenexButton( GenexButton(
label: '继续逛', label: context.t('paymentSuccess.continueBrowse'),
variant: GenexButtonVariant.outline, variant: GenexButtonVariant.outline,
onPressed: () { onPressed: () {
Navigator.pushNamedAndRemoveUntil(context, '/main', (route) => false); Navigator.pushNamedAndRemoveUntil(context, '/main', (route) => false);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -45,7 +46,7 @@ class _RedeemQrPageState extends State<RedeemQrPage> {
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.gray900, backgroundColor: AppColors.gray900,
foregroundColor: Colors.white, foregroundColor: Colors.white,
title: const Text('出示券码'), title: Text(context.t('redeem.title')),
), ),
body: Center( body: Center(
child: Column( child: Column(
@ -54,7 +55,7 @@ class _RedeemQrPageState extends State<RedeemQrPage> {
// Coupon Info // Coupon Info
Text('星巴克 \$25 礼品卡', style: AppTypography.h2.copyWith(color: Colors.white)), Text('星巴克 \$25 礼品卡', style: AppTypography.h2.copyWith(color: Colors.white)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('面值 \$25.00', style: AppTypography.bodyMedium.copyWith(color: Colors.white60)), Text('${context.t('redeem.faceValue')} \$25.00', style: AppTypography.bodyMedium.copyWith(color: Colors.white60)),
const SizedBox(height: 32), const SizedBox(height: 32),
// QR Code Area // QR Code Area
@ -103,7 +104,7 @@ class _RedeemQrPageState extends State<RedeemQrPage> {
const Icon(Icons.timer_outlined, color: Colors.white54, size: 18), const Icon(Icons.timer_outlined, color: Colors.white54, size: 18),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
'有效时间 $_formattedTime', '${context.t('redeem.validTime')} $_formattedTime',
style: AppTypography.bodyMedium.copyWith(color: Colors.white54), style: AppTypography.bodyMedium.copyWith(color: Colors.white54),
), ),
], ],
@ -113,7 +114,7 @@ class _RedeemQrPageState extends State<RedeemQrPage> {
onPressed: () { onPressed: () {
setState(() => _remainingSeconds = 300); setState(() => _remainingSeconds = 300);
}, },
child: Text('刷新券码', style: AppTypography.labelMedium.copyWith(color: AppColors.primaryLight)), child: Text(context.t('redeem.refresh'), style: AppTypography.labelMedium.copyWith(color: AppColors.primaryLight)),
), ),
const SizedBox(height: 40), const SizedBox(height: 40),
@ -131,7 +132,7 @@ class _RedeemQrPageState extends State<RedeemQrPage> {
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text( child: Text(
'请将此码出示给商户扫描,屏幕已自动调至最高亮度', context.t('redeem.showHint'),
style: AppTypography.caption.copyWith(color: Colors.white54), style: AppTypography.caption.copyWith(color: Colors.white54),
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -27,7 +28,7 @@ class _SearchPageState extends State<SearchPage> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: const Text('取消'), child: Text(context.t('search.cancel')),
), ),
], ],
), ),
@ -46,11 +47,11 @@ class _SearchPageState extends State<SearchPage> {
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
autofocus: true, autofocus: true,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '搜索券、品牌、分类...', hintText: context.t('search.hint'),
prefixIcon: Icon(Icons.search_rounded, size: 20), prefixIcon: const Icon(Icons.search_rounded, size: 20),
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 10), contentPadding: const EdgeInsets.symmetric(vertical: 10),
), ),
onChanged: (v) => setState(() => _hasInput = v.isNotEmpty), onChanged: (v) => setState(() => _hasInput = v.isNotEmpty),
), ),
@ -68,7 +69,7 @@ class _SearchPageState extends State<SearchPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('热门搜索', style: AppTypography.h3), Text(context.t('search.hotSearch'), style: AppTypography.h3),
GestureDetector(onTap: () {}, child: const Icon(Icons.refresh_rounded, size: 18, color: AppColors.textTertiary)), GestureDetector(onTap: () {}, child: const Icon(Icons.refresh_rounded, size: 18, color: AppColors.textTertiary)),
], ],
), ),
@ -76,7 +77,7 @@ class _SearchPageState extends State<SearchPage> {
Wrap( Wrap(
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: ['星巴克', 'Amazon', '餐饮券', '折扣券', '旅游', 'Nike'].map((tag) { children: ['Starbucks', 'Amazon', context.t('search.diningCoupon'), context.t('search.discountCoupon'), context.t('search.travel'), 'Nike'].map((tag) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
_searchController.text = tag; _searchController.text = tag;
@ -100,10 +101,10 @@ class _SearchPageState extends State<SearchPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('搜索历史', style: AppTypography.h3), Text(context.t('search.history'), style: AppTypography.h3),
GestureDetector( GestureDetector(
onTap: () {}, onTap: () {},
child: Text('清空', style: AppTypography.labelSmall.copyWith(color: AppColors.textTertiary)), child: Text(context.t('search.clear'), style: AppTypography.labelSmall.copyWith(color: AppColors.textTertiary)),
), ),
], ],
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -35,17 +36,17 @@ class _IssuerMainPageState extends State<IssuerMainPage> {
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex, selectedIndex: _currentIndex,
onDestinationSelected: (i) => setState(() => _currentIndex = i), onDestinationSelected: (i) => setState(() => _currentIndex = i),
destinations: const [ destinations: [
NavigationDestination(icon: Icon(Icons.dashboard_outlined), NavigationDestination(icon: const Icon(Icons.dashboard_outlined),
selectedIcon: Icon(Icons.dashboard_rounded), label: '总览'), selectedIcon: const Icon(Icons.dashboard_rounded), label: context.t('issuer.overview')),
NavigationDestination(icon: Icon(Icons.add_card_outlined), NavigationDestination(icon: const Icon(Icons.add_card_outlined),
selectedIcon: Icon(Icons.add_card_rounded), label: '发券'), selectedIcon: const Icon(Icons.add_card_rounded), label: context.t('issuer.issue')),
NavigationDestination(icon: Icon(Icons.fact_check_outlined), NavigationDestination(icon: const Icon(Icons.fact_check_outlined),
selectedIcon: Icon(Icons.fact_check_rounded), label: '核销'), selectedIcon: const Icon(Icons.fact_check_rounded), label: context.t('issuer.redeem')),
NavigationDestination(icon: Icon(Icons.account_balance_outlined), NavigationDestination(icon: const Icon(Icons.account_balance_outlined),
selectedIcon: Icon(Icons.account_balance_rounded), label: '财务'), selectedIcon: const Icon(Icons.account_balance_rounded), label: context.t('issuer.finance')),
NavigationDestination(icon: Icon(Icons.more_horiz_rounded), NavigationDestination(icon: const Icon(Icons.more_horiz_rounded),
selectedIcon: Icon(Icons.more_horiz_rounded), label: '更多'), selectedIcon: const Icon(Icons.more_horiz_rounded), label: context.t('issuer.more')),
], ],
), ),
floatingActionButton: AiFab( floatingActionButton: AiFab(
@ -64,7 +65,7 @@ class _IssuerDashboard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('发行方管理'), title: Text(context.t('issuer.title')),
actions: [ actions: [
IconButton(icon: const Icon(Icons.notifications_outlined), onPressed: () {}), IconButton(icon: const Icon(Icons.notifications_outlined), onPressed: () {}),
], ],
@ -113,7 +114,7 @@ class _IssuerDashboard extends StatelessWidget {
)), )),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('已认证发行方', style: AppTypography.bodySmall.copyWith( Text(context.t('issuer.verified'), style: AppTypography.bodySmall.copyWith(
color: Colors.white70, color: Colors.white70,
)), )),
], ],
@ -147,7 +148,7 @@ class _IssuerDashboard extends StatelessWidget {
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text( child: Text(
'AI建议:当前市场需求旺盛,建议增发 \$50 面值礼品卡', 'AI: Market demand is strong, recommend issuing \$50 gift cards',
style: AppTypography.bodySmall, style: AppTypography.bodySmall,
maxLines: 2, maxLines: 2,
), ),
@ -159,29 +160,29 @@ class _IssuerDashboard extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
// Stats Grid // Stats Grid
_buildStatsGrid(), _buildStatsGrid(context),
const SizedBox(height: 24), const SizedBox(height: 24),
// Quick Actions // Quick Actions
Text('快捷操作', style: AppTypography.h3), Text(context.t('issuer.quickActions'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
children: [ children: [
_quickAction(Icons.add_card_rounded, '创建券', AppColors.primary), _quickAction(Icons.add_card_rounded, context.t('issuer.createCoupon'), AppColors.primary),
const SizedBox(width: 12), const SizedBox(width: 12),
_quickAction(Icons.people_outline_rounded, '门店管理', AppColors.info), _quickAction(Icons.people_outline_rounded, context.t('issuer.storeManage'), AppColors.info),
const SizedBox(width: 12), const SizedBox(width: 12),
_quickAction(Icons.analytics_outlined, '销售分析', AppColors.success), _quickAction(Icons.analytics_outlined, context.t('issuer.salesAnalysis'), AppColors.success),
const SizedBox(width: 12), const SizedBox(width: 12),
_quickAction(Icons.download_rounded, '对账单', AppColors.warning), _quickAction(Icons.download_rounded, context.t('issuer.statement'), AppColors.warning),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Recent Coupons // Recent Coupons
Text('我的券', style: AppTypography.h3), Text(context.t('issuer.myCoupons'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
...List.generate(3, (i) => _couponItem(i)), ...List.generate(3, (i) => _couponItem(context, i)),
const SizedBox(height: 80), const SizedBox(height: 80),
], ],
@ -190,12 +191,12 @@ class _IssuerDashboard extends StatelessWidget {
); );
} }
Widget _buildStatsGrid() { Widget _buildStatsGrid(BuildContext context) {
final stats = [ final stats = [
('发行总量', '12,800', AppColors.primary), (context.t('issuer.totalIssued'), '12,800', AppColors.primary),
('已售出', '9,650', AppColors.success), (context.t('issuer.totalSold'), '9,650', AppColors.success),
('已核销', '6,240', AppColors.info), (context.t('issuer.totalRedeemed'), '6,240', AppColors.info),
('核销率', '64.7%', AppColors.warning), (context.t('issuer.redeemRate'), '64.7%', AppColors.warning),
]; ];
return GridView.count( return GridView.count(
@ -245,9 +246,13 @@ class _IssuerDashboard extends StatelessWidget {
); );
} }
Widget _couponItem(int index) { Widget _couponItem(BuildContext context, int index) {
final names = ['\$25 礼品卡', '\$50 满减券', '\$10 折扣券']; final names = ['\$25 Gift Card', '\$50 Voucher', '\$10 Discount'];
final statuses = ['已上架', '审核中', '已售罄']; final statuses = [
context.t('issuer.listed'),
context.t('issuer.underReview'),
context.t('issuer.soldOut'),
];
final colors = [AppColors.success, AppColors.warning, AppColors.textTertiary]; final colors = [AppColors.success, AppColors.warning, AppColors.textTertiary];
return Container( return Container(
@ -275,7 +280,7 @@ class _IssuerDashboard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(names[index], style: AppTypography.labelMedium), Text(names[index], style: AppTypography.labelMedium),
Text('发行 1,000 / 已售 ${[850, 0, 500][index]}', Text('${context.t('issuer.issuedSlash')} 1,000 / ${context.t('issuer.sold')} ${[850, 0, 500][index]}',
style: AppTypography.caption), style: AppTypography.caption),
], ],
), ),
@ -303,7 +308,7 @@ class _CouponCenter extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('发券中心')), appBar: AppBar(title: Text(context.t('issuer.issueCenter'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: AppSpacing.pagePadding, padding: AppSpacing.pagePadding,
child: Column( child: Column(
@ -312,7 +317,7 @@ class _CouponCenter extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// Template Selection // Template Selection
Text('选择券模板', style: AppTypography.h3), Text(context.t('issuer.selectTemplate'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
GridView.count( GridView.count(
crossAxisCount: 2, crossAxisCount: 2,
@ -322,10 +327,10 @@ class _CouponCenter extends StatelessWidget {
crossAxisSpacing: 12, crossAxisSpacing: 12,
childAspectRatio: 1.2, childAspectRatio: 1.2,
children: [ children: [
_templateCard('满减券', Icons.local_offer_rounded, AppColors.couponDining), _templateCard(context.t('issuer.voucherType'), Icons.local_offer_rounded, AppColors.couponDining),
_templateCard('折扣券', Icons.percent_rounded, AppColors.couponShopping), _templateCard(context.t('issuer.discountType'), Icons.percent_rounded, AppColors.couponShopping),
_templateCard('礼品卡', Icons.card_giftcard_rounded, AppColors.couponEntertainment), _templateCard(context.t('issuer.giftCardType'), Icons.card_giftcard_rounded, AppColors.couponEntertainment),
_templateCard('储值券', Icons.account_balance_wallet_rounded, AppColors.couponTravel), _templateCard(context.t('issuer.storedValueType'), Icons.account_balance_wallet_rounded, AppColors.couponTravel),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@ -334,13 +339,19 @@ class _CouponCenter extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('券管理', style: AppTypography.h3), Text(context.t('issuer.couponManage'), style: AppTypography.h3),
TextButton(onPressed: () {}, child: const Text('查看全部')), TextButton(onPressed: () {}, child: Text(context.t('issuer.viewAll'))),
], ],
), ),
...List.generate(5, (i) { ...List.generate(5, (i) {
final statusColors = [AppColors.success, AppColors.warning, AppColors.success, AppColors.textTertiary, AppColors.error]; final statusColors = [AppColors.success, AppColors.warning, AppColors.success, AppColors.textTertiary, AppColors.error];
final statuses = ['已上架', '审核中', '已上架', '已下架', '已售罄']; final statuses = [
context.t('issuer.listed'),
context.t('issuer.underReview'),
context.t('issuer.listed'),
context.t('issuer.unlisted'),
context.t('issuer.soldOut'),
];
return ListTile( return ListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
leading: Container( leading: Container(
@ -352,8 +363,8 @@ class _CouponCenter extends StatelessWidget {
child: const Icon(Icons.confirmation_number_outlined, child: const Icon(Icons.confirmation_number_outlined,
color: AppColors.primary, size: 20), color: AppColors.primary, size: 20),
), ),
title: Text('券活动 ${i + 1}', style: AppTypography.labelMedium), title: Text('${context.t('issuer.couponEvents')} ${i + 1}', style: AppTypography.labelMedium),
subtitle: Text('已售 ${(i + 1) * 120} / ${(i + 1) * 200}', subtitle: Text('${context.t('issuer.sold')} ${(i + 1) * 120} / ${(i + 1) * 200}',
style: AppTypography.caption), style: AppTypography.caption),
trailing: Container( trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
@ -374,12 +385,12 @@ class _CouponCenter extends StatelessWidget {
), ),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () { onPressed: () {
// Navigator: CreateCouponPage // Navigator: -> CreateCouponPage
}, },
backgroundColor: AppColors.primary, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: Colors.white,
icon: const Icon(Icons.add_rounded), icon: const Icon(Icons.add_rounded),
label: const Text('创建新券'), label: Text(context.t('issuer.createNew')),
), ),
); );
} }
@ -410,7 +421,7 @@ class _RedeemManagement extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('核销管理')), appBar: AppBar(title: Text(context.t('issuer.redeemManage'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: AppSpacing.pagePadding, padding: AppSpacing.pagePadding,
child: Column( child: Column(
@ -421,11 +432,11 @@ class _RedeemManagement extends StatelessWidget {
// Stats // Stats
Row( Row(
children: [ children: [
_stat('今日', '156笔', AppColors.primary), _stat(context.t('common.today'), '156', AppColors.primary),
const SizedBox(width: 12), const SizedBox(width: 12),
_stat('本周', '892笔', AppColors.success), _stat(context.t('common.thisWeek'), '892', AppColors.success),
const SizedBox(width: 12), const SizedBox(width: 12),
_stat('本月', '3,450笔', AppColors.info), _stat(context.t('common.thisMonth'), '3,450', AppColors.info),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@ -439,7 +450,7 @@ class _RedeemManagement extends StatelessWidget {
border: Border.all(color: AppColors.borderLight), border: Border.all(color: AppColors.borderLight),
), ),
child: Center( child: Center(
child: Text('核销趋势图 (fl_chart)', child: Text('${context.t('issuer.redeemTrend')} (fl_chart)',
style: AppTypography.bodySmall.copyWith(color: AppColors.textTertiary)), style: AppTypography.bodySmall.copyWith(color: AppColors.textTertiary)),
), ),
), ),
@ -449,8 +460,8 @@ class _RedeemManagement extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('门店管理', style: AppTypography.h3), Text(context.t('issuer.storeManage'), style: AppTypography.h3),
TextButton(onPressed: () {}, child: const Text('全部门店')), TextButton(onPressed: () {}, child: Text(context.t('issuer.allStores'))),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -470,13 +481,13 @@ class _RedeemManagement extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(['总部', '朝阳门店', '国贸门店'][i], Text(['HQ', 'Downtown Store', 'Mall Store'][i],
style: AppTypography.labelMedium), style: AppTypography.labelMedium),
Text('今日 ${[56, 23, 18][i]}', style: AppTypography.caption), Text('${context.t('common.today')} ${[56, 23, 18][i]}', style: AppTypography.caption),
], ],
), ),
), ),
Text('${[3, 2, 1][i]} 名员工', style: AppTypography.caption), Text('${[3, 2, 1][i]} ${context.t('issuer.employees')}', style: AppTypography.caption),
], ],
), ),
)), )),
@ -514,7 +525,7 @@ class _FinancePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('财务管理')), appBar: AppBar(title: Text(context.t('issuer.financeManage'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: AppSpacing.pagePadding, padding: AppSpacing.pagePadding,
child: Column( child: Column(
@ -532,7 +543,7 @@ class _FinancePage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('总销售额', style: AppTypography.bodySmall.copyWith( Text(context.t('issuer.totalSales'), style: AppTypography.bodySmall.copyWith(
color: Colors.white70, color: Colors.white70,
)), )),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -542,11 +553,11 @@ class _FinancePage extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
Row( Row(
children: [ children: [
_revenueItem('已到账', '\$98,200'), _revenueItem(context.t('issuer.settled'), '\$98,200'),
const SizedBox(width: 24), const SizedBox(width: 24),
_revenueItem('待结算', '\$24,250'), _revenueItem(context.t('issuer.pendingSettle'), '\$24,250'),
const SizedBox(width: 24), const SizedBox(width: 24),
_revenueItem('Breakage', '\$6,000'), _revenueItem(context.t('issuer.breakage'), '\$6,000'),
], ],
), ),
], ],
@ -559,7 +570,7 @@ class _FinancePage extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: GenexButton( child: GenexButton(
label: '提现', label: context.t('issuer.withdrawBtn'),
icon: Icons.account_balance_rounded, icon: Icons.account_balance_rounded,
onPressed: () {}, onPressed: () {},
), ),
@ -567,7 +578,7 @@ class _FinancePage extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: GenexButton( child: GenexButton(
label: '对账报表', label: context.t('issuer.reportBtn'),
icon: Icons.receipt_long_rounded, icon: Icons.receipt_long_rounded,
variant: GenexButtonVariant.outline, variant: GenexButtonVariant.outline,
onPressed: () {}, onPressed: () {},
@ -578,7 +589,7 @@ class _FinancePage extends StatelessWidget {
const SizedBox(height: 24), const SizedBox(height: 24),
// Settlement details // Settlement details
Text('结算明细', style: AppTypography.h3), Text(context.t('issuer.settleDetail'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
...List.generate(5, (i) => Container( ...List.generate(5, (i) => Container(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
@ -604,7 +615,7 @@ class _FinancePage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('核销结算 - \$25券 × ${(i + 1) * 5}', Text('Redeem Settlement - \$25 x ${(i + 1) * 5}',
style: AppTypography.labelSmall), style: AppTypography.labelSmall),
Text('02/${10 - i}', style: AppTypography.caption), Text('02/${10 - i}', style: AppTypography.caption),
], ],
@ -643,7 +654,7 @@ class _IssuerMore extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('更多')), appBar: AppBar(title: Text(context.t('issuer.more'))),
body: ListView( body: ListView(
padding: AppSpacing.pagePadding, padding: AppSpacing.pagePadding,
children: [ children: [
@ -663,7 +674,7 @@ class _IssuerMore extends StatelessWidget {
children: [ children: [
const Icon(Icons.verified_rounded, color: AppColors.creditAAA), const Icon(Icons.verified_rounded, color: AppColors.creditAAA),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('信用等级', style: AppTypography.labelMedium), Text(context.t('issuer.creditLevel'), style: AppTypography.labelMedium),
const Spacer(), const Spacer(),
const CreditBadge(rating: 'AAA', size: CreditBadgeSize.large), const CreditBadge(rating: 'AAA', size: CreditBadgeSize.large),
], ],
@ -675,14 +686,14 @@ class _IssuerMore extends StatelessWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('发行额度', style: AppTypography.caption), Text(context.t('issuer.issueQuota'), style: AppTypography.caption),
Text('\$500,000', style: AppTypography.h2.copyWith(color: AppColors.primary)), Text('\$500,000', style: AppTypography.h2.copyWith(color: AppColors.primary)),
], ],
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text('已用额度', style: AppTypography.caption), Text(context.t('issuer.usedQuota'), style: AppTypography.caption),
Text('\$128,450', style: AppTypography.h3), Text('\$128,450', style: AppTypography.h3),
], ],
), ),
@ -704,13 +715,13 @@ class _IssuerMore extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
// Menu items // Menu items
_menuItem(Icons.bar_chart_rounded, '数据中心', '发行量/销量/兑付率'), _menuItem(Icons.bar_chart_rounded, context.t('issuer.dataCenter'), context.t('issuer.issueSalesRate')),
_menuItem(Icons.people_rounded, '用户画像', '购买用户分布分析'), _menuItem(Icons.people_rounded, context.t('issuer.userProfile'), context.t('issuer.userProfileDesc')),
_menuItem(Icons.shield_outlined, '信用详情', '评分详情与提升建议'), _menuItem(Icons.shield_outlined, context.t('issuer.creditDetail'), context.t('issuer.creditDetailDesc')),
_menuItem(Icons.history_rounded, '额度变动', '历史额度调整记录'), _menuItem(Icons.history_rounded, context.t('issuer.quotaChange'), context.t('issuer.quotaChangeDesc')),
_menuItem(Icons.business_rounded, '企业信息', '营业执照/联系人'), _menuItem(Icons.business_rounded, context.t('issuer.companyInfo'), context.t('issuer.companyInfoDesc')),
_menuItem(Icons.settings_outlined, '设置', '通知/安全/语言'), _menuItem(Icons.settings_outlined, context.t('issuer.settingsItem'), context.t('issuer.settingsItemDesc')),
_menuItem(Icons.help_outline_rounded, '帮助中心', '常见问题与客服'), _menuItem(Icons.help_outline_rounded, context.t('issuer.helpItem'), context.t('issuer.helpItemDesc')),
], ],
), ),
); );

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -37,13 +38,13 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('AI 助手'), title: Text(context.t('merchantAi.title')),
bottom: TabBar( bottom: TabBar(
controller: _tabController, controller: _tabController,
tabs: const [ tabs: [
Tab(text: '核销辅助'), Tab(text: context.t('merchantAi.redeemAssist')),
Tab(text: '客流预测'), Tab(text: context.t('merchantAi.trafficForecast')),
Tab(text: '异常预警'), Tab(text: context.t('merchantAi.anomalyAlert')),
], ],
labelColor: AppColors.primary, labelColor: AppColors.primary,
unselectedLabelColor: AppColors.textTertiary, unselectedLabelColor: AppColors.textTertiary,
@ -92,10 +93,10 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
Widget _buildAiQuickActions() { Widget _buildAiQuickActions() {
final actions = [ final actions = [
('验券真伪', Icons.verified_user_rounded, AppColors.success), (context.t('merchantAi.verifyAuth'), Icons.verified_user_rounded, AppColors.success),
('查券状态', Icons.search_rounded, AppColors.info), (context.t('merchantAi.checkStatus'), Icons.search_rounded, AppColors.info),
('批量核销', Icons.playlist_add_check_rounded, AppColors.primary), (context.t('merchantAi.batchRedeem'), Icons.playlist_add_check_rounded, AppColors.primary),
('问题反馈', Icons.feedback_rounded, AppColors.warning), (context.t('merchantAi.feedback'), Icons.feedback_rounded, AppColors.warning),
]; ];
return Container( return Container(
@ -117,12 +118,12 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
borderRadius: AppSpacing.borderRadiusSm, borderRadius: AppSpacing.borderRadiusSm,
), ),
child: child:
const Center(child: Text('', style: TextStyle(fontSize: 16))), const Center(child: Text('AI', style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold))),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
const Text( Text(
'AI 快捷操作', context.t('merchantAi.quickActions'),
style: TextStyle( style: const TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white), color: Colors.white),
@ -183,23 +184,23 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
const Icon(Icons.lightbulb_outline_rounded, const Icon(Icons.lightbulb_outline_rounded,
color: AppColors.warning, size: 20), color: AppColors.warning, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('核销提示', style: AppTypography.labelLarge), Text(context.t('merchantAi.redeemTips'), style: AppTypography.labelLarge),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildTipItem( _buildTipItem(
'星巴克 \$25 礼品卡有批次更新', 'Starbucks \$25 Gift Card batch update',
'新批次(#B2026-03)已上线,请注意核验二维码格式', 'New batch (#B2026-03) is live, verify QR format',
AppColors.info, AppColors.info,
), ),
_buildTipItem( _buildTipItem(
'午间高峰期即将到来', 'Lunch rush approaching',
'预计 11:30-13:00 核销量将达峰值 ~15笔/小时', 'Expected 11:30-13:00 peak ~15 redeems/hour',
AppColors.warning, AppColors.warning,
), ),
_buildTipItem( _buildTipItem(
'本店暂不支持 Nike 体验券', 'Nike Experience Voucher not supported',
'该券仅限旗舰店核销,请引导顾客至正确门店', 'Only redeemable at flagship stores',
AppColors.error, AppColors.error,
), ),
], ],
@ -241,9 +242,9 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
Widget _buildHotCouponsToday() { Widget _buildHotCouponsToday() {
final hotCoupons = [ final hotCoupons = [
('星巴克 \$25 礼品卡', 12, AppColors.couponDining), ('Starbucks \$25 Gift Card', 12, AppColors.couponDining),
('Amazon \$50 购物券', 8, AppColors.couponShopping), ('Amazon \$50 Voucher', 8, AppColors.couponShopping),
('电影票 \$12', 5, AppColors.couponEntertainment), ('Movie Ticket \$12', 5, AppColors.couponEntertainment),
]; ];
return Container( return Container(
@ -261,7 +262,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
const Icon(Icons.local_fire_department_rounded, const Icon(Icons.local_fire_department_rounded,
color: AppColors.error, size: 20), color: AppColors.error, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('今日热门核销', style: AppTypography.labelLarge), Text(context.t('merchantAi.todayHotRedeem'), style: AppTypography.labelLarge),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@ -292,7 +293,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
borderRadius: AppSpacing.borderRadiusFull, borderRadius: AppSpacing.borderRadiusFull,
), ),
child: Text( child: Text(
'$count', '$count${context.t('merchantAi.countUnit')}',
style: AppTypography.labelSmall style: AppTypography.labelSmall
.copyWith(color: AppColors.primary), .copyWith(color: AppColors.primary),
), ),
@ -321,18 +322,18 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
const Icon(Icons.auto_awesome_rounded, const Icon(Icons.auto_awesome_rounded,
color: AppColors.primary, size: 20), color: AppColors.primary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('AI 营销建议', style: AppTypography.labelLarge), Text(context.t('merchantAi.aiMarketing'), style: AppTypography.labelLarge),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildSuggestionItem( _buildSuggestionItem(
'推荐搭配销售', context.t('merchantAi.crossSellTitle'),
'购买咖啡券的顾客同时对糕点券感兴趣,建议推荐组合', context.t('merchantAi.crossSellDesc'),
Icons.restaurant_rounded, Icons.restaurant_rounded,
), ),
_buildSuggestionItem( _buildSuggestionItem(
'周末促销建议', context.t('merchantAi.weekendPromoTitle'),
'历史数据显示周六核销量+30%,建议推出周末限时活动', context.t('merchantAi.weekendPromoDesc'),
Icons.campaign_rounded, Icons.campaign_rounded,
), ),
], ],
@ -404,13 +405,13 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
), ),
child: Column( child: Column(
children: [ children: [
const Row( Row(
children: [ children: [
Icon(Icons.insights_rounded, color: Colors.white, size: 22), const Icon(Icons.insights_rounded, color: Colors.white, size: 22),
SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
'今日客流预测', context.t('merchantAi.todayForecast'),
style: TextStyle( style: const TextStyle(
fontSize: 17, fontSize: 17,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: Colors.white), color: Colors.white),
@ -421,9 +422,9 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_predictionStat('预计核销', '45笔'), _predictionStat(context.t('merchantAi.expectedRedeem'), '45${context.t('merchantAi.countUnit')}'),
_predictionStat('高峰时段', '11:30-13:00'), _predictionStat(context.t('merchantAi.peakHours'), '11:30-13:00'),
_predictionStat('预计收入', '\$892'), _predictionStat(context.t('merchantAi.expectedRevenue'), '\$892'),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -435,12 +436,11 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
), ),
child: Row( child: Row(
children: [ children: [
const Text('', const Icon(Icons.auto_awesome_rounded, color: Colors.white70, size: 14),
style: TextStyle(fontSize: 14)),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'较上周同期增长12%建议午间增加1名收银员', context.t('merchantAi.trafficInsight'),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.white.withValues(alpha: 0.9), color: Colors.white.withValues(alpha: 0.9),
@ -475,16 +475,9 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
Widget _buildHourlyBreakdown() { Widget _buildHourlyBreakdown() {
final hours = [ final hours = [
('9:00', 3), ('9:00', 3), ('10:00', 5), ('11:00', 8), ('12:00', 12),
('10:00', 5), ('13:00', 9), ('14:00', 4), ('15:00', 3), ('16:00', 2),
('11:00', 8), ('17:00', 5), ('18:00', 7),
('12:00', 12),
('13:00', 9),
('14:00', 4),
('15:00', 3),
('16:00', 2),
('17:00', 5),
('18:00', 7),
]; ];
final maxCount = 12; final maxCount = 12;
@ -498,7 +491,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('分时段预测', style: AppTypography.labelLarge), Text(context.t('merchantAi.hourlyForecast'), style: AppTypography.labelLarge),
const SizedBox(height: 16), const SizedBox(height: 16),
...hours.map((h) { ...hours.map((h) {
final (time, count) = h; final (time, count) = h;
@ -510,9 +503,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
children: [ children: [
SizedBox( SizedBox(
width: 44, width: 44,
child: Text(time, child: Text(time, style: AppTypography.caption.copyWith(fontFamily: 'monospace')),
style: AppTypography.caption
.copyWith(fontFamily: 'monospace')),
), ),
Expanded( Expanded(
child: ClipRRect( child: ClipRRect(
@ -520,9 +511,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: pct, value: pct,
backgroundColor: AppColors.gray100, backgroundColor: AppColors.gray100,
valueColor: AlwaysStoppedAnimation( valueColor: AlwaysStoppedAnimation(isPeak ? AppColors.primary : AppColors.primaryLight),
isPeak ? AppColors.primary : AppColors.primaryLight,
),
minHeight: 16, minHeight: 16,
), ),
), ),
@ -531,7 +520,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
SizedBox( SizedBox(
width: 30, width: 30,
child: Text( child: Text(
'$count', '$count${context.t('merchantAi.countUnit')}',
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: isPeak ? FontWeight.w600 : FontWeight.w400, fontWeight: isPeak ? FontWeight.w600 : FontWeight.w400,
@ -550,13 +539,13 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
Widget _buildWeeklyForecast() { Widget _buildWeeklyForecast() {
final days = [ final days = [
('周一', 38, false), (context.t('merchantAi.monday'), 38, false),
('周二', 42, false), (context.t('merchantAi.tuesday'), 42, false),
('周三', 45, true), (context.t('merchantAi.wednesday'), 45, true),
('周四', 40, false), (context.t('merchantAi.thursday'), 40, false),
('周五', 52, false), (context.t('merchantAi.friday'), 52, false),
('周六', 68, false), (context.t('merchantAi.saturday'), 68, false),
('周日', 55, false), (context.t('merchantAi.sunday'), 55, false),
]; ];
return Container( return Container(
@ -569,7 +558,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('本周预测', style: AppTypography.labelLarge), Text(context.t('merchantAi.weeklyForecast'), style: AppTypography.labelLarge),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
@ -593,8 +582,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
height: 80 * heightPct, height: 80 * heightPct,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isToday ? AppColors.primary : AppColors.primarySurface, color: isToday ? AppColors.primary : AppColors.primarySurface,
borderRadius: borderRadius: const BorderRadius.vertical(top: Radius.circular(4)),
const BorderRadius.vertical(top: Radius.circular(4)),
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -627,16 +615,15 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
children: [ children: [
Row( Row(
children: [ children: [
const Icon(Icons.people_alt_rounded, const Icon(Icons.people_alt_rounded, color: AppColors.primary, size: 20),
color: AppColors.primary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('排班建议', style: AppTypography.labelLarge), Text(context.t('merchantAi.staffSuggestion'), style: AppTypography.labelLarge),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_staffRow('上午 (9:00-13:00)', '建议 2 人', '含午间高峰'), _staffRow('AM (9:00-13:00)', '2 staff', 'Lunch peak'),
_staffRow('下午 (13:00-17:00)', '建议 1 人', '客流较少'), _staffRow('PM (13:00-17:00)', '1 staff', 'Low traffic'),
_staffRow('傍晚 (17:00-21:00)', '建议 2 人', '下班高峰'), _staffRow('EVE (17:00-21:00)', '2 staff', 'Evening peak'),
], ],
), ),
); );
@ -647,20 +634,9 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
padding: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.symmetric(vertical: 6),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(flex: 3, child: Text(period, style: AppTypography.bodySmall)),
flex: 3, Expanded(flex: 2, child: Text(suggestion, style: AppTypography.labelSmall.copyWith(color: AppColors.primary))),
child: Text(period, style: AppTypography.bodySmall), Expanded(flex: 2, child: Text(reason, style: AppTypography.caption)),
),
Expanded(
flex: 2,
child: Text(suggestion,
style: AppTypography.labelSmall
.copyWith(color: AppColors.primary)),
),
Expanded(
flex: 2,
child: Text(reason, style: AppTypography.caption),
),
], ],
), ),
); );
@ -675,19 +651,12 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Alert Summary
_buildAlertSummary(), _buildAlertSummary(),
const SizedBox(height: 20), const SizedBox(height: 20),
// Active Alerts
_buildActiveAlerts(), _buildActiveAlerts(),
const SizedBox(height: 20), const SizedBox(height: 20),
// Suspicious Patterns
_buildSuspiciousPatterns(), _buildSuspiciousPatterns(),
const SizedBox(height: 20), const SizedBox(height: 20),
// Recent Resolved
_buildResolvedAlerts(), _buildResolvedAlerts(),
], ],
), ),
@ -697,11 +666,11 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
Widget _buildAlertSummary() { Widget _buildAlertSummary() {
return Row( return Row(
children: [ children: [
_alertStatCard('待处理', '2', AppColors.error), _alertStatCard(context.t('merchantAi.pendingCount'), '2', AppColors.error),
const SizedBox(width: 12), const SizedBox(width: 12),
_alertStatCard('今日已处理', '5', AppColors.success), _alertStatCard(context.t('merchantAi.resolvedToday'), '5', AppColors.success),
const SizedBox(width: 12), const SizedBox(width: 12),
_alertStatCard('风险指数', '', AppColors.info), _alertStatCard(context.t('merchantAi.riskIndex'), context.t('merchantAi.riskLow'), AppColors.info),
], ],
); );
} }
@ -717,9 +686,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
), ),
child: Column( child: Column(
children: [ children: [
Text(value, Text(value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: color)),
style: TextStyle(
fontSize: 22, fontWeight: FontWeight.w700, color: color)),
const SizedBox(height: 2), const SizedBox(height: 2),
Text(label, style: AppTypography.caption.copyWith(color: color)), Text(label, style: AppTypography.caption.copyWith(color: color)),
], ],
@ -741,27 +708,24 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
children: [ children: [
Row( Row(
children: [ children: [
const Icon(Icons.warning_amber_rounded, const Icon(Icons.warning_amber_rounded, color: AppColors.error, size: 20),
color: AppColors.error, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('活跃预警', Text(context.t('merchantAi.activeAlerts'), style: AppTypography.labelLarge.copyWith(color: AppColors.error)),
style:
AppTypography.labelLarge.copyWith(color: AppColors.error)),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_alertItem( _alertItem(
'高频核销检测', context.t('merchantAi.highFreqRedeem'),
'用户#78901 在 5 分钟内尝试核销 3 张同品牌券', 'User #78901 attempted 3 redeems of same brand in 5 minutes',
'2 分钟前', '2 min ago',
AppColors.error, AppColors.error,
Icons.speed_rounded, Icons.speed_rounded,
), ),
const Divider(height: 20), const Divider(height: 20),
_alertItem( _alertItem(
'疑似伪造券码', context.t('merchantAi.suspectFakeCode'),
'券码 GNX-FAKE-001 格式异常,不在系统记录中', 'Code GNX-FAKE-001 format abnormal, not in system',
'15 分钟前', '15 min ago',
AppColors.warning, AppColors.warning,
Icons.gpp_bad_rounded, Icons.gpp_bad_rounded,
), ),
@ -770,16 +734,14 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
); );
} }
Widget _alertItem( Widget _alertItem(String title, String desc, String time, Color color, IconData icon) {
String title, String desc, String time, Color color, IconData icon) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
width: 36, width: 36, height: 36,
height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withValues(alpha: 0.1), color: color.withValues(alpha: 0.1),
borderRadius: AppSpacing.borderRadiusSm, borderRadius: AppSpacing.borderRadiusSm,
@ -817,28 +779,23 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
children: [ children: [
Row( Row(
children: [ children: [
const Icon(Icons.pattern_rounded, const Icon(Icons.pattern_rounded, color: AppColors.warning, size: 20),
color: AppColors.warning, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('可疑模式检测', style: AppTypography.labelLarge), Text(context.t('merchantAi.suspiciousPatterns'), style: AppTypography.labelLarge),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_patternItem( _patternItem(context.t('merchantAi.consecutiveRedeem'), '3x/5min (threshold: 2x/5min)', 0.8, AppColors.error),
'同一用户连续核销', '3次/5分钟 (阈值: 2次/5分钟)', 0.8, AppColors.error),
const SizedBox(height: 10), const SizedBox(height: 10),
_patternItem( _patternItem(context.t('merchantAi.offHoursRedeem'), '0x/week', 0.0, AppColors.success),
'非营业时间核销尝试', '0次/本周', 0.0, AppColors.success),
const SizedBox(height: 10), const SizedBox(height: 10),
_patternItem( _patternItem(context.t('merchantAi.expiredRedeemAttempt'), '2x/today', 0.4, AppColors.warning),
'过期券核销尝试', '2次/今日', 0.4, AppColors.warning),
], ],
), ),
); );
} }
Widget _patternItem( Widget _patternItem(String label, String detail, double severity, Color color) {
String label, String detail, double severity, Color color) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -854,12 +811,11 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
), ),
child: Text( child: Text(
severity > 0.6 severity > 0.6
? '异常' ? context.t('merchantAi.statusAbnormal')
: severity > 0.2 : severity > 0.2
? '注意' ? context.t('merchantAi.statusWarning')
: '正常', : context.t('merchantAi.statusNormal'),
style: TextStyle( style: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: color),
fontSize: 10, fontWeight: FontWeight.w600, color: color),
), ),
), ),
], ],
@ -891,13 +847,13 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('今日已处理', style: AppTypography.labelLarge), Text(context.t('merchantAi.resolvedToday'), style: AppTypography.labelLarge),
const SizedBox(height: 12), const SizedBox(height: 12),
_resolvedItem('过期券核销拦截', '系统自动拦截', '10:24'), _resolvedItem(context.t('merchantAi.expiredBlock'), 'Auto-blocked', '10:24'),
_resolvedItem('重复核销拦截', '同一券码二次扫描', '11:05'), _resolvedItem(context.t('merchantAi.duplicateBlock'), 'Same code scanned twice', '11:05'),
_resolvedItem('非本店券提醒', '引导至正确门店', '12:30'), _resolvedItem(context.t('merchantAi.wrongStoreAlert'), 'Directed to correct store', '12:30'),
_resolvedItem('余额不足核销', '告知顾客充值', '13:15'), _resolvedItem(context.t('merchantAi.insufficientBalance'), 'Customer notified', '13:15'),
_resolvedItem('系统超时重试', '网络恢复后自动完成', '14:02'), _resolvedItem(context.t('merchantAi.systemRetry'), 'Auto-completed on reconnect', '14:02'),
], ],
), ),
); );
@ -908,8 +864,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
padding: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.symmetric(vertical: 6),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.check_circle_rounded, const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 16),
color: AppColors.success, size: 16),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Column( child: Column(
@ -920,9 +875,7 @@ class _MerchantAiAssistantPageState extends State<MerchantAiAssistantPage>
], ],
), ),
), ),
Text(time, Text(time, style: AppTypography.caption.copyWith(fontFamily: 'monospace')),
style: AppTypography.caption
.copyWith(fontFamily: 'monospace')),
], ],
), ),
); );

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -20,10 +21,10 @@ class MerchantHomePage extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
// Header // Header
_buildHeader(), _buildHeader(context),
// Network Status // Network Status
_buildNetworkStatus(isOnline: true), _buildNetworkStatus(context, isOnline: true),
// Main Scanner Area // Main Scanner Area
Expanded(child: _buildScannerArea(context)), Expanded(child: _buildScannerArea(context)),
@ -36,7 +37,7 @@ class MerchantHomePage extends StatelessWidget {
); );
} }
Widget _buildHeader() { Widget _buildHeader(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 12), padding: const EdgeInsets.fromLTRB(20, 12, 20, 12),
child: Row( child: Row(
@ -55,8 +56,8 @@ class MerchantHomePage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('星巴克 朝阳门店', style: AppTypography.labelMedium), Text('Starbucks Store', style: AppTypography.labelMedium),
Text('收银员 - 张三', style: AppTypography.caption), Text('${context.t('merchant.redeemOperator')} - Staff', style: AppTypography.caption),
], ],
), ),
), ),
@ -72,7 +73,7 @@ class MerchantHomePage extends StatelessWidget {
children: [ children: [
const Icon(Icons.check_circle_rounded, size: 14, color: AppColors.success), const Icon(Icons.check_circle_rounded, size: 14, color: AppColors.success),
const SizedBox(width: 4), const SizedBox(width: 4),
Text('今日 23 笔', style: AppTypography.labelSmall.copyWith( Text('${context.t('merchant.today')} 23 ${context.t('merchant.syncUnit')}', style: AppTypography.labelSmall.copyWith(
color: AppColors.success, color: AppColors.success,
)), )),
], ],
@ -83,7 +84,7 @@ class MerchantHomePage extends StatelessWidget {
); );
} }
Widget _buildNetworkStatus({required bool isOnline}) { Widget _buildNetworkStatus(BuildContext context, {required bool isOnline}) {
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 20), margin: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
@ -103,7 +104,9 @@ class MerchantHomePage extends StatelessWidget {
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
isOnline ? '在线模式' : '离线模式 - 待同步 3 笔', isOnline
? context.t('merchant.onlineMode')
: '${context.t('merchant.offlineMode')} - ${context.t('merchant.pendingSync')} 3 ${context.t('merchant.syncUnit')}',
style: AppTypography.caption.copyWith( style: AppTypography.caption.copyWith(
color: isOnline ? AppColors.success : AppColors.warning, color: isOnline ? AppColors.success : AppColors.warning,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -153,7 +156,7 @@ class MerchantHomePage extends StatelessWidget {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
'将券二维码对准扫描框', context.t('merchant.scanHint'),
style: AppTypography.bodyMedium.copyWith(color: Colors.white70), style: AppTypography.bodyMedium.copyWith(color: Colors.white70),
), ),
], ],
@ -182,7 +185,7 @@ class MerchantHomePage extends StatelessWidget {
color: Colors.white70, size: 22), color: Colors.white70, size: 22),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('手电筒', style: AppTypography.caption.copyWith(color: Colors.white54)), Text(context.t('merchant.flashlight'), style: AppTypography.caption.copyWith(color: Colors.white54)),
], ],
), ),
), ),
@ -247,16 +250,16 @@ class MerchantHomePage extends StatelessWidget {
padding: const EdgeInsets.fromLTRB(20, 12, 20, 16), padding: const EdgeInsets.fromLTRB(20, 12, 20, 16),
child: Row( child: Row(
children: [ children: [
_bottomAction(Icons.keyboard_rounded, '手动输码', () { _bottomAction(Icons.keyboard_rounded, context.t('merchant.manualInput'), () {
_showManualInput(context); _showManualInput(context);
}), }),
const SizedBox(width: 16), const SizedBox(width: 16),
_bottomAction(Icons.history_rounded, '核销记录', () { _bottomAction(Icons.history_rounded, context.t('merchant.redeemRecords'), () {
// Navigator: RedeemHistoryPage // Navigator: -> RedeemHistoryPage
}), }),
const SizedBox(width: 16), const SizedBox(width: 16),
_bottomAction(Icons.bar_chart_rounded, '门店数据', () { _bottomAction(Icons.bar_chart_rounded, context.t('merchant.storeData'), () {
// Navigator: StoreDashboardPage // Navigator: -> StoreDashboardPage
}), }),
], ],
), ),
@ -310,20 +313,20 @@ class MerchantHomePage extends StatelessWidget {
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text('手动输入券码', style: AppTypography.h2), Text(context.t('merchant.inputCode'), style: AppTypography.h2),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
autofocus: true, autofocus: true,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '请输入券码', hintText: context.t('merchant.inputCodeHint'),
prefixIcon: Icon(Icons.confirmation_number_outlined, prefixIcon: const Icon(Icons.confirmation_number_outlined,
color: AppColors.textTertiary), color: AppColors.textTertiary),
), ),
textCapitalization: TextCapitalization.characters, textCapitalization: TextCapitalization.characters,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
GenexButton( GenexButton(
label: '查询', label: context.t('merchant.query'),
onPressed: () {}, onPressed: () {},
), ),
], ],
@ -369,8 +372,8 @@ class RedeemConfirmSheet extends StatelessWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('用户昵称', style: AppTypography.labelMedium), Text(context.t('merchant.userNickname'), style: AppTypography.labelMedium),
Text('消费者', style: AppTypography.caption), Text(context.t('merchant.consumer'), style: AppTypography.caption),
], ],
), ),
], ],
@ -387,20 +390,20 @@ class RedeemConfirmSheet extends StatelessWidget {
), ),
child: Column( child: Column(
children: [ children: [
_row('券名称', '星巴克 \$25 礼品卡'), _row(context, context.t('merchant.couponName'), 'Starbucks \$25 Gift Card'),
const SizedBox(height: 8), const SizedBox(height: 8),
_row('面值', '\$25.00'), _row(context, context.t('merchant.faceValue'), '\$25.00'),
const SizedBox(height: 8), const SizedBox(height: 8),
_row('有效期', '2026/12/31'), _row(context, context.t('merchant.validUntil'), '2026/12/31'),
const SizedBox(height: 8), const SizedBox(height: 8),
_row('使用条件', '无最低消费'), _row(context, context.t('merchant.useCondition'), context.t('merchant.noMinSpend')),
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
GenexButton( GenexButton(
label: '确认核销', label: context.t('merchant.confirmRedeem'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
// Show success // Show success
@ -408,7 +411,7 @@ class RedeemConfirmSheet extends StatelessWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
GenexButton( GenexButton(
label: '取消', label: context.t('common.cancel'),
variant: GenexButtonVariant.text, variant: GenexButtonVariant.text,
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
@ -417,7 +420,7 @@ class RedeemConfirmSheet extends StatelessWidget {
); );
} }
Widget _row(String label, String value) { Widget _row(BuildContext context, String label, String value) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -456,14 +459,14 @@ class RedeemSuccessSheet extends StatelessWidget {
child: const Icon(Icons.check_rounded, color: Colors.white, size: 36), child: const Icon(Icons.check_rounded, color: Colors.white, size: 36),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text('核销成功', style: AppTypography.h1), Text(context.t('merchant.redeemSuccess'), style: AppTypography.h1),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('星巴克 \$25 礼品卡', style: AppTypography.bodyMedium.copyWith( Text('Starbucks \$25 Gift Card', style: AppTypography.bodyMedium.copyWith(
color: AppColors.textSecondary, color: AppColors.textSecondary,
)), )),
const SizedBox(height: 32), const SizedBox(height: 32),
GenexButton( GenexButton(
label: '继续核销', label: context.t('merchant.continueRedeem'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
], ],
@ -484,11 +487,11 @@ class RedeemHistoryPage extends StatelessWidget {
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20), icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
title: const Text('核销记录'), title: Text(context.t('merchant.redeemRecords')),
actions: [ actions: [
TextButton( TextButton(
onPressed: () {}, onPressed: () {},
child: Text('今日', style: AppTypography.labelSmall.copyWith( child: Text(context.t('common.today'), style: AppTypography.labelSmall.copyWith(
color: AppColors.primary, color: AppColors.primary,
)), )),
), ),
@ -526,15 +529,15 @@ class RedeemHistoryPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('品牌 ${index + 1} \$${(index + 1) * 10} ', Text('Brand ${index + 1} \$${(index + 1) * 10} Voucher',
style: AppTypography.labelSmall), style: AppTypography.labelSmall),
Text('核销员: 张三 · 14:${30 + index}', Text('${context.t('merchant.redeemOperator')}: Staff · 14:${30 + index}',
style: AppTypography.caption), style: AppTypography.caption),
], ],
), ),
), ),
Text( Text(
isSync ? '已同步' : '待同步', isSync ? context.t('merchant.synced') : context.t('merchant.pendingSyncLabel'),
style: AppTypography.caption.copyWith( style: AppTypography.caption.copyWith(
color: isSync ? AppColors.success : AppColors.warning, color: isSync ? AppColors.success : AppColors.warning,
), ),
@ -560,7 +563,7 @@ class StoreDashboardPage extends StatelessWidget {
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20), icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
title: const Text('门店数据'), title: Text(context.t('merchant.storeData')),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: AppSpacing.pagePadding, padding: AppSpacing.pagePadding,
@ -572,15 +575,15 @@ class StoreDashboardPage extends StatelessWidget {
// Today Stats // Today Stats
Row( Row(
children: [ children: [
_statCard('今日核销', '23笔', Icons.check_circle_rounded, AppColors.success), _statCard(context.t('merchant.todayRedeem'), '23${context.t('merchant.syncUnit')}', Icons.check_circle_rounded, AppColors.success),
const SizedBox(width: 12), const SizedBox(width: 12),
_statCard('核销金额', '\$1,456', Icons.attach_money_rounded, AppColors.primary), _statCard(context.t('merchant.redeemAmount'), '\$1,456', Icons.attach_money_rounded, AppColors.primary),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Weekly Trend (placeholder) // Weekly Trend (placeholder)
Text('本周趋势', style: AppTypography.h3), Text(context.t('merchant.weekTrend'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
height: 200, height: 200,
@ -590,17 +593,17 @@ class StoreDashboardPage extends StatelessWidget {
border: Border.all(color: AppColors.borderLight), border: Border.all(color: AppColors.borderLight),
), ),
child: Center( child: Center(
child: Text('周核销趋势图 (fl_chart)', child: Text('Weekly Redeem Trend (fl_chart)',
style: AppTypography.bodySmall.copyWith(color: AppColors.textTertiary)), style: AppTypography.bodySmall.copyWith(color: AppColors.textTertiary)),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Staff Ranking // Staff Ranking
Text('核销员排行', style: AppTypography.h3), Text(context.t('merchant.operatorRank'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
...List.generate(3, (index) { ...List.generate(3, (index) {
final names = ['张三', '李四', '王五']; final names = ['Staff A', 'Staff B', 'Staff C'];
final counts = [12, 8, 3]; final counts = [12, 8, 3];
return Container( return Container(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
@ -631,7 +634,7 @@ class StoreDashboardPage extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Text(names[index], style: AppTypography.labelMedium), Text(names[index], style: AppTypography.labelMedium),
const Spacer(), const Spacer(),
Text('${counts[index]}', style: AppTypography.bodyMedium.copyWith( Text('${counts[index]}${context.t('merchant.syncUnit')}', style: AppTypography.bodyMedium.copyWith(
color: AppColors.primary, color: AppColors.primary,
)), )),
], ],

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -8,19 +9,20 @@ import '../../../../app/theme/app_spacing.dart';
/// ///
/// ///
class MessageDetailPage extends StatelessWidget { class MessageDetailPage extends StatelessWidget {
final String title; final String? title;
final String type; final String type;
const MessageDetailPage({ const MessageDetailPage({
super.key, super.key,
this.title = '交易成功通知', this.title,
this.type = 'transaction', this.type = 'transaction',
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final displayTitle = title ?? context.t('message.tradeSuccess');
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('消息详情')), appBar: AppBar(title: Text(context.t('message.detailTitle'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
@ -45,16 +47,16 @@ class MessageDetailPage extends StatelessWidget {
color: _typeColor.withValues(alpha: 0.1), color: _typeColor.withValues(alpha: 0.1),
borderRadius: AppSpacing.borderRadiusFull, borderRadius: AppSpacing.borderRadiusFull,
), ),
child: Text(_typeLabel, style: TextStyle(fontSize: 11, color: _typeColor, fontWeight: FontWeight.w600)), child: Text(_getTypeLabel(context), style: TextStyle(fontSize: 11, color: _typeColor, fontWeight: FontWeight.w600)),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Title // Title
Text(title, style: AppTypography.h1), Text(displayTitle, style: AppTypography.h1),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('2026年2月10日 14:32', style: AppTypography.bodySmall), Text('2026-02-10 14:32', style: AppTypography.bodySmall),
const SizedBox(height: 24), const SizedBox(height: 24),
// Content // Content
@ -65,19 +67,19 @@ class MessageDetailPage extends StatelessWidget {
borderRadius: AppSpacing.borderRadiusMd, borderRadius: AppSpacing.borderRadiusMd,
border: Border.all(color: AppColors.borderLight), border: Border.all(color: AppColors.borderLight),
), ),
child: const Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( const Text(
'您成功购买了 星巴克 \$25 礼品卡,支付金额 \$21.25。', 'You purchased Starbucks \$25 Gift Card for \$21.25.',
style: TextStyle(fontSize: 15, height: 1.6), style: TextStyle(fontSize: 15, height: 1.6),
), ),
SizedBox(height: 16), const SizedBox(height: 16),
_DetailRow('券名称', '星巴克 \$25 礼品卡'), _DetailRow(context.t('message.couponName'), 'Starbucks \$25 Gift Card'),
_DetailRow('面值', '\$25.00'), _DetailRow(context.t('message.faceValue'), '\$25.00'),
_DetailRow('支付金额', '\$21.25'), _DetailRow(context.t('message.payAmount'), '\$21.25'),
_DetailRow('订单号', 'GNX20260210001'), _DetailRow(context.t('message.orderNo'), 'GNX20260210001'),
_DetailRow('支付方式', 'Visa •••• 4242'), _DetailRow(context.t('message.payMethod'), 'Visa •••• 4242'),
], ],
), ),
), ),
@ -88,7 +90,7 @@ class MessageDetailPage extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: OutlinedButton( child: OutlinedButton(
onPressed: () {}, onPressed: () {},
child: const Text('查看券详情'), child: Text(context.t('message.viewCouponDetail')),
), ),
), ),
], ],
@ -115,12 +117,12 @@ class MessageDetailPage extends StatelessWidget {
} }
} }
String get _typeLabel { String _getTypeLabel(BuildContext context) {
switch (type) { switch (type) {
case 'transaction': return '交易通知'; case 'transaction': return context.t('message.tradeNotify');
case 'expiry': return '到期提醒'; case 'expiry': return context.t('message.expiryRemind');
case 'system': return '系统通知'; case 'system': return context.t('message.systemNotify');
default: return '活动推送'; default: return context.t('message.promoNotify');
} }
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -35,22 +36,22 @@ class _MessagePageState extends State<MessagePage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('消息'), title: Text(context.t('message.title')),
actions: [ actions: [
TextButton( TextButton(
onPressed: () {}, onPressed: () {},
child: Text('全部已读', style: AppTypography.labelSmall.copyWith( child: Text(context.t('message.markAllRead'), style: AppTypography.labelSmall.copyWith(
color: AppColors.primary, color: AppColors.primary,
)), )),
), ),
], ],
bottom: TabBar( bottom: TabBar(
controller: _tabController, controller: _tabController,
tabs: const [ tabs: [
Tab(text: '全部'), Tab(text: context.t('common.all')),
Tab(text: '交易'), Tab(text: context.t('message.tabTrade')),
Tab(text: '到期'), Tab(text: context.t('message.tabExpiry')),
Tab(text: '公告'), Tab(text: context.t('message.tabAnnouncement')),
], ],
), ),
), ),
@ -68,7 +69,7 @@ class _MessagePageState extends State<MessagePage>
Widget _buildMessageList({bool all = false, MessageType? type}) { Widget _buildMessageList({bool all = false, MessageType? type}) {
if (type == MessageType.announcement) { if (type == MessageType.announcement) {
return EmptyState.noMessages(); return EmptyState.noMessages(context: context);
} }
final messages = _mockMessages final messages = _mockMessages
@ -178,36 +179,36 @@ class _MockMessage {
const _mockMessages = [ const _mockMessages = [
_MockMessage( _MockMessage(
'购买成功', 'Purchase Successful',
'您已成功购买 星巴克 \$25 礼品卡,共花费 \$21.25', 'You have successfully purchased Starbucks \$25 Gift Card for \$21.25',
'14:32', '14:32',
MessageType.transaction, MessageType.transaction,
false, false,
), ),
_MockMessage( _MockMessage(
'券即将到期', 'Coupon Expiring Soon',
'您持有的 Target \$30 折扣券 将于3天后到期请及时使用', 'Your Target \$30 Voucher will expire in 3 days',
'10:15', '10:15',
MessageType.expiry, MessageType.expiry,
false, false,
), ),
_MockMessage( _MockMessage(
'价格提醒', 'Price Alert',
'您关注的 Amazon \$100 购物券 当前价格已降至 \$82低于您设定的提醒价格', 'Amazon \$100 Voucher price dropped to \$82, below your alert price',
'昨天', 'Yesterday',
MessageType.price, MessageType.price,
true, true,
), ),
_MockMessage( _MockMessage(
'出售成交', 'Sale Completed',
'您挂单出售的 Nike \$80 运动券 已成功售出,收入 \$68.00', 'Your listed Nike \$80 Voucher has been sold for \$68.00',
'02/07', '02/07',
MessageType.transaction, MessageType.transaction,
true, true,
), ),
_MockMessage( _MockMessage(
'核销成功', 'Redeem Successful',
'Walmart \$50 生活券 已在门店核销成功', 'Walmart \$50 Voucher redeemed at store',
'02/06', '02/06',
MessageType.transaction, MessageType.transaction,
true, true,

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -13,34 +14,37 @@ class KycPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('身份认证')), appBar: AppBar(title: Text(context.t('kyc.title'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
children: [ children: [
// Current Level // Current Level
_buildCurrentLevel(), _buildCurrentLevel(context),
const SizedBox(height: 24), const SizedBox(height: 24),
// KYC Levels // KYC Levels
_buildLevel( _buildLevel(
'L1 基础认证', context,
'手机号 + 邮箱验证', context.t('kyc.l1Title'),
['每日购买限额 \$500', '可购买券、出示核销'], context.t('kyc.l1Desc'),
[context.t('kyc.l1Limit'), context.t('kyc.l1Feature')],
true, true,
AppColors.success, AppColors.success,
), ),
_buildLevel( _buildLevel(
'L2 身份认证', context,
'身份证/护照验证', context.t('kyc.l2Title'),
['每日购买限额 \$5,000', '解锁二级市场交易、P2P转赠'], context.t('kyc.l2Desc'),
[context.t('kyc.l2Limit'), context.t('kyc.l2Feature')],
false, false,
AppColors.info, AppColors.info,
), ),
_buildLevel( _buildLevel(
'L3 高级认证', context,
'视频面审 + 地址证明', context.t('kyc.l3Title'),
['无限额', '解锁大额交易、提现无限制'], context.t('kyc.l3Desc'),
[context.t('kyc.l3Limit'), context.t('kyc.l3Feature')],
false, false,
AppColors.primary, AppColors.primary,
), ),
@ -50,7 +54,7 @@ class KycPage extends StatelessWidget {
); );
} }
Widget _buildCurrentLevel() { Widget _buildCurrentLevel(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -73,11 +77,11 @@ class KycPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('当前认证等级', style: AppTypography.bodySmall.copyWith(color: Colors.white70)), Text(context.t('kyc.currentLevel'), style: AppTypography.bodySmall.copyWith(color: Colors.white70)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('L1 基础认证', style: AppTypography.h1.copyWith(color: Colors.white)), Text(context.t('kyc.l1Title'), style: AppTypography.h1.copyWith(color: Colors.white)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('每日购买限额 \$500', style: AppTypography.bodySmall.copyWith(color: Colors.white60)), Text(context.t('kyc.l1Limit'), style: AppTypography.bodySmall.copyWith(color: Colors.white60)),
], ],
), ),
), ),
@ -87,6 +91,7 @@ class KycPage extends StatelessWidget {
} }
Widget _buildLevel( Widget _buildLevel(
BuildContext context,
String title, String title,
String requirement, String requirement,
List<String> benefits, List<String> benefits,
@ -136,7 +141,7 @@ class KycPage extends StatelessWidget {
color: AppColors.successLight, color: AppColors.successLight,
borderRadius: AppSpacing.borderRadiusFull, borderRadius: AppSpacing.borderRadiusFull,
), ),
child: Text('已完成', style: AppTypography.caption.copyWith(color: AppColors.success)), child: Text(context.t('kyc.completed'), style: AppTypography.caption.copyWith(color: AppColors.success)),
) )
else else
ElevatedButton( ElevatedButton(
@ -145,7 +150,7 @@ class KycPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
minimumSize: Size.zero, minimumSize: Size.zero,
), ),
child: const Text('去认证', style: TextStyle(fontSize: 13)), child: Text(context.t('kyc.goVerify'), style: const TextStyle(fontSize: 13)),
), ),
], ],
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -12,11 +13,11 @@ class PaymentManagementPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('支付管理')), appBar: AppBar(title: Text(context.t('payManage.title'))),
body: ListView( body: ListView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
children: [ children: [
Text('我的银行卡', style: AppTypography.h3), Text(context.t('payManage.myCards'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
// Card List // Card List
@ -32,19 +33,19 @@ class PaymentManagementPage extends StatelessWidget {
border: Border.all(color: AppColors.border, style: BorderStyle.solid), border: Border.all(color: AppColors.border, style: BorderStyle.solid),
borderRadius: AppSpacing.borderRadiusMd, borderRadius: AppSpacing.borderRadiusMd,
), ),
child: const Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.add_circle_outline_rounded, color: AppColors.primary), const Icon(Icons.add_circle_outline_rounded, color: AppColors.primary),
SizedBox(width: 8), const SizedBox(width: 8),
Text('添加新银行卡', style: TextStyle(color: AppColors.primary, fontWeight: FontWeight.w600)), Text(context.t('payManage.addCard'), style: const TextStyle(color: AppColors.primary, fontWeight: FontWeight.w600)),
], ],
), ),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
// Bank Account // Bank Account
Text('银行账户(提现用)', style: AppTypography.h3), Text(context.t('payManage.bankAccount'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
padding: AppSpacing.cardPadding, padding: AppSpacing.cardPadding,
@ -62,7 +63,7 @@ class PaymentManagementPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Bank of America', style: AppTypography.labelMedium), Text('Bank of America', style: AppTypography.labelMedium),
Text('•••• 6789 · 储蓄账户', style: AppTypography.caption), Text('•••• 6789 · ${context.t('withdraw.savingsAccount')}', style: AppTypography.caption),
], ],
), ),
), ),
@ -73,11 +74,11 @@ class PaymentManagementPage extends StatelessWidget {
const SizedBox(height: 32), const SizedBox(height: 32),
// Payment Security // Payment Security
Text('支付安全', style: AppTypography.h3), Text(context.t('payManage.paymentSecurity'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildSettingTile('支付密码', '已设置', Icons.password_rounded), _buildSettingTile(context.t('payManage.paymentPassword'), context.t('payManage.passwordSet'), Icons.password_rounded),
_buildSettingTile('指纹/面容支付', '已开启', Icons.fingerprint_rounded), _buildSettingTile(context.t('payManage.biometricPay'), context.t('payManage.biometricEnabled'), Icons.fingerprint_rounded),
_buildSettingTile('免密支付', '单笔≤\$10', Icons.flash_on_rounded), _buildSettingTile(context.t('payManage.noPasswordPay'), context.t('payManage.noPasswordLimit'), Icons.flash_on_rounded),
], ],
), ),
); );

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -27,7 +28,7 @@ class _ProModePageState extends State<ProModePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('高级模式')), appBar: AppBar(title: Text(context.t('proMode.title'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
@ -90,7 +91,7 @@ class _ProModePageState extends State<ProModePage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'高级模式 (Pro)', '${context.t('proMode.title')} (Pro)',
style: TextStyle( style: TextStyle(
fontSize: 17, fontSize: 17,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@ -99,7 +100,7 @@ class _ProModePageState extends State<ProModePage> {
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
'开启后可查看链上信息和连接外部钱包', context.t('proMode.toggleDesc'),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: _proModeEnabled ? Colors.white70 : AppColors.textSecondary, color: _proModeEnabled ? Colors.white70 : AppColors.textSecondary,
@ -124,9 +125,9 @@ class _ProModePageState extends State<ProModePage> {
color: Colors.white.withValues(alpha: 0.15), color: Colors.white.withValues(alpha: 0.15),
borderRadius: AppSpacing.borderRadiusFull, borderRadius: AppSpacing.borderRadiusFull,
), ),
child: const Text( child: Text(
'需要 KYC L2 及以上认证', context.t('proMode.requireKycL2'),
style: TextStyle(fontSize: 11, color: Colors.white70), style: const TextStyle(fontSize: 11, color: Colors.white70),
), ),
), ),
], ],
@ -159,7 +160,7 @@ class _ProModePageState extends State<ProModePage> {
color: AppColors.successLight, color: AppColors.successLight,
borderRadius: AppSpacing.borderRadiusFull, borderRadius: AppSpacing.borderRadiusFull,
), ),
child: const Text('已连接', style: TextStyle(fontSize: 11, color: AppColors.success, fontWeight: FontWeight.w600)), child: Text(context.t('proMode.connected'), style: const TextStyle(fontSize: 11, color: AppColors.success, fontWeight: FontWeight.w600)),
), ),
], ],
), ),
@ -186,7 +187,7 @@ class _ProModePageState extends State<ProModePage> {
), ),
TextButton( TextButton(
onPressed: () => setState(() => _walletConnected = false), onPressed: () => setState(() => _walletConnected = false),
child: const Text('断开', style: TextStyle(color: AppColors.error, fontSize: 13)), child: Text(context.t('proMode.disconnect'), style: const TextStyle(color: AppColors.error, fontSize: 13)),
), ),
], ],
), ),
@ -197,12 +198,12 @@ class _ProModePageState extends State<ProModePage> {
child: OutlinedButton.icon( child: OutlinedButton.icon(
onPressed: () => setState(() => _walletConnected = true), onPressed: () => setState(() => _walletConnected = true),
icon: const Icon(Icons.link_rounded, size: 18), icon: const Icon(Icons.link_rounded, size: 18),
label: const Text('连接外部钱包'), label: Text(context.t('proMode.connectWallet')),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'连接外部钱包后可将平台资产提取至自有地址', context.t('proMode.walletDesc'),
style: AppTypography.caption, style: AppTypography.caption,
), ),
], ],
@ -221,8 +222,8 @@ class _ProModePageState extends State<ProModePage> {
child: Column( child: Column(
children: [ children: [
SwitchListTile( SwitchListTile(
title: Text('显示链上地址', style: AppTypography.labelMedium), title: Text(context.t('proMode.showChainAddress'), style: AppTypography.labelMedium),
subtitle: Text('在券详情中展示合约地址', style: AppTypography.caption), subtitle: Text(context.t('proMode.showChainAddressDesc'), style: AppTypography.caption),
value: _showChainAddress, value: _showChainAddress,
onChanged: (v) => setState(() => _showChainAddress = v), onChanged: (v) => setState(() => _showChainAddress = v),
activeColor: AppColors.primary, activeColor: AppColors.primary,
@ -230,8 +231,8 @@ class _ProModePageState extends State<ProModePage> {
), ),
const Divider(height: 1), const Divider(height: 1),
SwitchListTile( SwitchListTile(
title: Text('显示交易Hash', style: AppTypography.labelMedium), title: Text(context.t('proMode.showTxHash'), style: AppTypography.labelMedium),
subtitle: Text('在交易记录中展示链上Hash', style: AppTypography.caption), subtitle: Text(context.t('proMode.showTxHashDesc'), style: AppTypography.caption),
value: _showTxHash, value: _showTxHash,
onChanged: (v) => setState(() => _showTxHash = v), onChanged: (v) => setState(() => _showTxHash = v),
activeColor: AppColors.primary, activeColor: AppColors.primary,
@ -257,18 +258,18 @@ class _ProModePageState extends State<ProModePage> {
children: [ children: [
const Icon(Icons.explore_rounded, color: AppColors.primary, size: 20), const Icon(Icons.explore_rounded, color: AppColors.primary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('交易浏览器', style: AppTypography.labelLarge), Text(context.t('proMode.txExplorer'), style: AppTypography.labelLarge),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildTxItem('购买 星巴克 \$25 礼品卡', '0xabc1...def3', '已确认', AppColors.success), _buildTxItem(context.t('proMode.txBuyExample'), '0xabc1...def3', context.t('proMode.confirmed'), AppColors.success),
_buildTxItem('出售 Amazon \$100 券', '0x789a...bc12', '已确认', AppColors.success), _buildTxItem(context.t('proMode.txSellExample'), '0x789a...bc12', context.t('proMode.confirmed'), AppColors.success),
_buildTxItem('转赠给 Alice', '0xdef4...5678', '确认中', AppColors.warning), _buildTxItem('Transfer to Alice', '0xdef4...5678', context.t('proMode.confirming'), AppColors.warning),
const SizedBox(height: 8), const SizedBox(height: 8),
Center( Center(
child: TextButton( child: TextButton(
onPressed: () {}, onPressed: () {},
child: const Text('查看全部链上交易'), child: Text(context.t('proMode.viewAllTx')),
), ),
), ),
], ],
@ -318,19 +319,19 @@ class _ProModePageState extends State<ProModePage> {
children: [ children: [
const Icon(Icons.token_rounded, color: AppColors.primary, size: 20), const Icon(Icons.token_rounded, color: AppColors.primary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('链上资产', style: AppTypography.labelLarge), Text(context.t('proMode.chainAssets'), style: AppTypography.labelLarge),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildAssetRow('平台托管钱包', '0x1234...abcd', '5 张券'), _buildAssetRow(context.t('proMode.custodialWallet'), '0x1234...abcd', context.t('proMode.couponCount5')),
if (_walletConnected) _buildAssetRow('外部钱包 (MetaMask)', '0x7a3b...c4f2', '0 张券'), if (_walletConnected) _buildAssetRow(context.t('proMode.externalWallet'), '0x7a3b...c4f2', context.t('proMode.couponCount0')),
const SizedBox(height: 12), const SizedBox(height: 12),
if (_walletConnected) if (_walletConnected)
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: OutlinedButton( child: OutlinedButton(
onPressed: () {}, onPressed: () {},
child: const Text('提取至外部钱包'), child: Text(context.t('proMode.extractToWallet')),
), ),
), ),
], ],
@ -373,16 +374,16 @@ class _ProModePageState extends State<ProModePage> {
children: [ children: [
const Icon(Icons.swap_horiz_rounded, color: AppColors.primary, size: 20), const Icon(Icons.swap_horiz_rounded, color: AppColors.primary, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Text('交易轨道', style: AppTypography.labelLarge), Text(context.t('proMode.tradeTrack'), style: AppTypography.labelLarge),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildTrackOption('Utility Track', '券有效期≤12个月无需证券牌照', AppColors.success, true), _buildTrackOption('Utility Track', context.t('proMode.utilityTrackDesc'), AppColors.success, true),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildTrackOption('Securities Track', '长期投资型券产品(即将推出)', AppColors.warning, false), _buildTrackOption('Securities Track', context.t('proMode.securitiesTrackDesc'), AppColors.warning, false),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'当前MVP版本仅支持Utility Track', context.t('proMode.mvpNote'),
style: AppTypography.caption.copyWith(color: AppColors.textTertiary), style: AppTypography.caption.copyWith(color: AppColors.textTertiary),
), ),
], ],
@ -419,7 +420,7 @@ class _ProModePageState extends State<ProModePage> {
), ),
), ),
if (active) Icon(Icons.check_circle_rounded, color: color, size: 20), if (active) Icon(Icons.check_circle_rounded, color: color, size: 20),
if (!active) Text('敬请期待', style: AppTypography.caption.copyWith(color: AppColors.textTertiary)), if (!active) Text(context.t('proMode.comingSoon'), style: AppTypography.caption.copyWith(color: AppColors.textTertiary)),
], ],
), ),
); );
@ -438,15 +439,10 @@ class _ProModePageState extends State<ProModePage> {
children: [ children: [
const Icon(Icons.info_outline_rounded, color: AppColors.textTertiary, size: 40), const Icon(Icons.info_outline_rounded, color: AppColors.textTertiary, size: 40),
const SizedBox(height: 12), const SizedBox(height: 12),
Text('什么是高级模式?', style: AppTypography.h3), Text(context.t('proMode.whatIsTitle'), style: AppTypography.h3),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'高级模式面向有区块链经验的用户,开启后可以:\n' context.t('proMode.whatIsDesc'),
'• 连接外部钱包MetaMask等\n'
'• 查看链上地址和交易Hash\n'
'• 将资产提取至自有钱包\n'
'• 查看底层链上数据\n\n'
'需要完成 KYC L2 认证后方可开启。',
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary, height: 1.6), style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary, height: 1.6),
), ),
], ],

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -20,33 +21,33 @@ class ProfilePage extends StatelessWidget {
SliverToBoxAdapter(child: _buildProfileHeader(context)), SliverToBoxAdapter(child: _buildProfileHeader(context)),
// Quick Stats // Quick Stats
SliverToBoxAdapter(child: _buildQuickStats()), SliverToBoxAdapter(child: _buildQuickStats(context)),
// Menu Sections // Menu Sections
SliverToBoxAdapter(child: _buildMenuSection('账户', [ SliverToBoxAdapter(child: _buildMenuSection(context.t('profile.account'), [
_MenuItem(Icons.verified_user_outlined, 'KYC 认证', '已完成 L1 认证', true, _MenuItem(Icons.verified_user_outlined, context.t('profile.kyc'), 'L1', true,
onTap: () => Navigator.pushNamed(context, '/kyc')), onTap: () => Navigator.pushNamed(context, '/kyc')),
_MenuItem(Icons.credit_card_rounded, '支付管理', '已绑定 2 张卡', true, _MenuItem(Icons.credit_card_rounded, context.t('profile.paymentManage'), 'Visa •••• 4242', true,
onTap: () => Navigator.pushNamed(context, '/payment/manage')), onTap: () => Navigator.pushNamed(context, '/payment/manage')),
_MenuItem(Icons.account_balance_wallet_outlined, '我的余额', '\$1,234.56', true, _MenuItem(Icons.account_balance_wallet_outlined, context.t('profile.walletBalance'), '\$1,234.56', true,
onTap: () => Navigator.pushNamed(context, '/wallet')), onTap: () => Navigator.pushNamed(context, '/wallet')),
])), ])),
SliverToBoxAdapter(child: _buildMenuSection('交易', [ SliverToBoxAdapter(child: _buildMenuSection(context.t('profile.trade'), [
_MenuItem(Icons.receipt_long_rounded, '交易记录', '', true, _MenuItem(Icons.receipt_long_rounded, context.t('profile.myTrades'), '', true,
onTap: () => Navigator.pushNamed(context, '/trading')), onTap: () => Navigator.pushNamed(context, '/trading')),
_MenuItem(Icons.storefront_rounded, '我的挂单', '2笔出售中', true, _MenuItem(Icons.storefront_rounded, context.t('tradingPage.pendingOrders'), '', true,
onTap: () => Navigator.pushNamed(context, '/trading')), onTap: () => Navigator.pushNamed(context, '/trading')),
_MenuItem(Icons.favorite_border_rounded, '我的收藏', '', true), _MenuItem(Icons.favorite_border_rounded, context.t('profile.myFavorites'), '', true),
])), ])),
SliverToBoxAdapter(child: _buildMenuSection('设置', [ SliverToBoxAdapter(child: _buildMenuSection(context.t('profile.settings'), [
_MenuItem(Icons.notifications_outlined, '通知设置', '', true), _MenuItem(Icons.notifications_outlined, context.t('settings.notifications'), '', true),
_MenuItem(Icons.language_rounded, '语言', '简体中文', true), _MenuItem(Icons.language_rounded, context.t('settings.language'), context.t('profile.simplifiedChinese'), true),
_MenuItem(Icons.shield_outlined, '安全设置', '', true), _MenuItem(Icons.shield_outlined, context.t('profile.securitySettings'), '', true),
_MenuItem(Icons.tune_rounded, '高级设置', 'Pro模式', true, _MenuItem(Icons.tune_rounded, context.t('profile.advancedSettings'), context.t('profile.proMode'), true,
onTap: () => Navigator.pushNamed(context, '/pro-mode')), onTap: () => Navigator.pushNamed(context, '/pro-mode')),
_MenuItem(Icons.info_outline_rounded, '关于 Genex', 'v1.0.0', true), _MenuItem(Icons.info_outline_rounded, context.t('profile.aboutGenex'), 'v1.0.0', true),
])), ])),
// Logout // Logout
@ -57,7 +58,7 @@ class ProfilePage extends StatelessWidget {
onPressed: () { onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil('/', (_) => false); Navigator.of(context).pushNamedAndRemoveUntil('/', (_) => false);
}, },
child: Text('退出登录', style: AppTypography.labelMedium.copyWith( child: Text(context.t('profile.logout'), style: AppTypography.labelMedium.copyWith(
color: AppColors.error, color: AppColors.error,
)), )),
), ),
@ -97,7 +98,7 @@ class ProfilePage extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
Text('用户昵称', style: AppTypography.h2.copyWith(color: Colors.white)), Text('User', style: AppTypography.h2.copyWith(color: Colors.white)),
const SizedBox(width: 8), const SizedBox(width: 8),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
@ -120,7 +121,7 @@ class ProfilePage extends StatelessWidget {
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('信用积分: 750', style: AppTypography.bodySmall.copyWith( Text('${context.t('profile.creditScore')}: 750', style: AppTypography.bodySmall.copyWith(
color: Colors.white70, color: Colors.white70,
)), )),
], ],
@ -139,12 +140,12 @@ class ProfilePage extends StatelessWidget {
); );
} }
Widget _buildQuickStats() { Widget _buildQuickStats(BuildContext context) {
final stats = [ final stats = [
('持券', '12'), (context.t('profile.holdCoupons'), '12'),
('交易', '28'), (context.t('profile.trade'), '28'),
('节省', '\$156'), (context.t('profile.saved'), '\$156'),
('信用', '750'), (context.t('profile.credit'), '750'),
]; ];
return Container( return Container(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -11,47 +12,47 @@ class SettingsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('设置')), appBar: AppBar(title: Text(context.t('settings.title'))),
body: ListView( body: ListView(
children: [ children: [
// Account & Security // Account & Security
_buildSection('账号与安全', [ _buildSection(context.t('settings.accountSecurity'), [
_buildTile('手机号', subtitle: '138****8888', icon: Icons.phone_rounded), _buildTile(context.t('settings.phone'), subtitle: '138****8888', icon: Icons.phone_rounded),
_buildTile('邮箱', subtitle: 'u***@email.com', icon: Icons.email_rounded), _buildTile(context.t('settings.email'), subtitle: 'u***@email.com', icon: Icons.email_rounded),
_buildTile('修改密码', icon: Icons.lock_rounded), _buildTile(context.t('settings.changePassword'), icon: Icons.lock_rounded),
_buildTile('身份认证', subtitle: 'L1 基础认证', icon: Icons.verified_user_rounded, onTap: () { _buildTile(context.t('settings.identity'), subtitle: 'L1', icon: Icons.verified_user_rounded, onTap: () {
Navigator.pushNamed(context, '/kyc'); Navigator.pushNamed(context, '/kyc');
}), }),
]), ]),
// Payment // Payment
_buildSection('支付管理', [ _buildSection(context.t('settings.paymentManage'), [
_buildTile('支付方式', subtitle: 'Visa •••• 4242', icon: Icons.credit_card_rounded), _buildTile(context.t('settings.paymentMethod'), subtitle: 'Visa •••• 4242', icon: Icons.credit_card_rounded),
_buildTile('银行账户', subtitle: 'BoA •••• 6789', icon: Icons.account_balance_rounded), _buildTile(context.t('settings.bankAccount'), subtitle: 'BoA •••• 6789', icon: Icons.account_balance_rounded),
_buildTile('支付密码', icon: Icons.password_rounded), _buildTile(context.t('settings.paymentPassword'), icon: Icons.password_rounded),
]), ]),
// Notifications // Notifications
_buildSection('通知设置', [ _buildSection(context.t('settings.notifications'), [
_buildSwitchTile('交易通知', true), _buildSwitchTile(context.t('settings.tradeNotify'), true),
_buildSwitchTile('到期提醒', true), _buildSwitchTile(context.t('settings.expiryRemind'), true),
_buildSwitchTile('行情变动', false), _buildSwitchTile(context.t('settings.marketChange'), false),
_buildSwitchTile('营销推送', false), _buildSwitchTile(context.t('settings.marketingPush'), false),
]), ]),
// General // General
_buildSection('通用', [ _buildSection(context.t('settings.general'), [
_buildTile('语言', subtitle: '简体中文', icon: Icons.language_rounded), _buildTile(context.t('settings.language'), subtitle: context.t('profile.simplifiedChinese'), icon: Icons.language_rounded),
_buildTile('货币', subtitle: 'USD', icon: Icons.attach_money_rounded), _buildTile(context.t('settings.currency'), subtitle: 'USD', icon: Icons.attach_money_rounded),
_buildTile('清除缓存', icon: Icons.cleaning_services_rounded), _buildTile(context.t('settings.clearCache'), icon: Icons.cleaning_services_rounded),
]), ]),
// About // About
_buildSection('关于', [ _buildSection(context.t('settings.about'), [
_buildTile('版本', subtitle: 'v1.0.0', icon: Icons.info_outline_rounded), _buildTile(context.t('settings.version'), subtitle: 'v1.0.0', icon: Icons.info_outline_rounded),
_buildTile('用户协议', icon: Icons.description_rounded), _buildTile(context.t('settings.userAgreement'), icon: Icons.description_rounded),
_buildTile('隐私政策', icon: Icons.privacy_tip_rounded), _buildTile(context.t('settings.privacyPolicy'), icon: Icons.privacy_tip_rounded),
_buildTile('帮助中心', icon: Icons.help_outline_rounded), _buildTile(context.t('settings.helpCenter'), icon: Icons.help_outline_rounded),
]), ]),
// Logout // Logout
@ -66,7 +67,7 @@ class SettingsPage extends StatelessWidget {
side: const BorderSide(color: AppColors.error), side: const BorderSide(color: AppColors.error),
minimumSize: const Size(double.infinity, 48), minimumSize: const Size(double.infinity, 48),
), ),
child: const Text('退出登录'), child: Text(context.t('settings.logout')),
), ),
), ),
], ],

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -20,13 +21,13 @@ class _SellOrderPageState extends State<SellOrderPage> {
double get _price => double.tryParse(_priceController.text) ?? 0; double get _price => double.tryParse(_priceController.text) ?? 0;
double get _discount => _faceValue > 0 ? _price / _faceValue * 100 : 0; double get _discount => _faceValue > 0 ? _price / _faceValue * 100 : 0;
double get _fee => _price * 0.015; // 1.5% double get _fee => _price * 0.015; // 1.5%
double get _receive => _price - _fee; double get _receive => _price - _fee;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('挂单出售')), appBar: AppBar(title: Text(context.t('sellOrder.title'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
@ -58,7 +59,7 @@ class _SellOrderPageState extends State<SellOrderPage> {
children: [ children: [
Text('星巴克 \$25 礼品卡', style: AppTypography.labelLarge), Text('星巴克 \$25 礼品卡', style: AppTypography.labelLarge),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('面值 \$$_faceValue · 信用 AAA', style: AppTypography.bodySmall), Text('${context.t('sellOrder.faceValue')} \$$_faceValue · ${context.t('sellOrder.credit')} AAA', style: AppTypography.bodySmall),
], ],
), ),
), ),
@ -68,14 +69,14 @@ class _SellOrderPageState extends State<SellOrderPage> {
const SizedBox(height: 24), const SizedBox(height: 24),
// Price Input // Price Input
Text('设定售价', style: AppTypography.h3), Text(context.t('sellOrder.setPrice'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
TextField( TextField(
controller: _priceController, controller: _priceController,
keyboardType: const TextInputType.numberWithOptions(decimal: true), keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration( decoration: InputDecoration(
prefixText: '\$ ', prefixText: '\$ ',
labelText: '售价', labelText: context.t('sellOrder.price'),
suffixText: 'USD', suffixText: 'USD',
), ),
style: AppTypography.priceLarge, style: AppTypography.priceLarge,
@ -96,7 +97,7 @@ class _SellOrderPageState extends State<SellOrderPage> {
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'AI建议售价\$22.509折此价格成交概率最高', '${context.t('sellOrder.aiSuggest')}: \$22.50 (90%), ${context.t('sellOrder.bestDealRate')}',
style: AppTypography.caption.copyWith(color: AppColors.primary), style: AppTypography.caption.copyWith(color: AppColors.primary),
), ),
), ),
@ -115,11 +116,11 @@ class _SellOrderPageState extends State<SellOrderPage> {
), ),
child: Column( child: Column(
children: [ children: [
_buildRow('售价', '\$${_price.toStringAsFixed(2)}'), _buildRow(context.t('sellOrder.price'), '\$${_price.toStringAsFixed(2)}'),
_buildRow('折扣率', '${_discount.toStringAsFixed(1)}%'), _buildRow(context.t('sellOrder.discountRate'), '${_discount.toStringAsFixed(1)}%'),
_buildRow('平台手续费 (1.5%)', '-\$${_fee.toStringAsFixed(2)}'), _buildRow(context.t('sellOrder.platformFee'), '-\$${_fee.toStringAsFixed(2)}'),
const Divider(height: 24), const Divider(height: 24),
_buildRow('预计到账', '\$${_receive.toStringAsFixed(2)}', isBold: true), _buildRow(context.t('sellOrder.estimatedReceive'), '\$${_receive.toStringAsFixed(2)}', isBold: true),
], ],
), ),
), ),
@ -138,7 +139,7 @@ class _SellOrderPageState extends State<SellOrderPage> {
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'当前市场均价 \$22.80 · 最近24小时成交 42 笔', '${context.t('sellOrder.marketAvg')} \$22.80 · ${context.t('sellOrder.recent24hTrades')} 42 ${context.t('sellOrder.tradesUnit')}',
style: AppTypography.caption.copyWith(color: AppColors.info), style: AppTypography.caption.copyWith(color: AppColors.info),
), ),
), ),
@ -154,7 +155,7 @@ class _SellOrderPageState extends State<SellOrderPage> {
height: AppSpacing.buttonHeight, height: AppSpacing.buttonHeight,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => _confirmSell(context), onPressed: () => _confirmSell(context),
child: const Text('确认挂单'), child: Text(context.t('sellOrder.confirmList')),
), ),
), ),
), ),
@ -178,10 +179,10 @@ class _SellOrderPageState extends State<SellOrderPage> {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('挂单成功'), title: Text(context.t('sellOrder.success')),
content: const Text('您的券已挂到市场,当有买家下单时将自动成交。'), content: Text(context.t('sellOrder.successHint')),
actions: [ actions: [
TextButton(onPressed: () { Navigator.pop(ctx); Navigator.pop(context); }, child: const Text('确定')), TextButton(onPressed: () { Navigator.pop(ctx); Navigator.pop(context); }, child: Text(context.t('sellOrder.ok'))),
], ],
), ),
); );

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -35,12 +36,12 @@ class _TradingPageState extends State<TradingPage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('我的交易'), title: Text(context.t('tradingPage.title')),
bottom: TabBar( bottom: TabBar(
controller: _tabController, controller: _tabController,
tabs: const [ tabs: [
Tab(text: '我的挂单'), Tab(text: context.t('tradingPage.pendingOrders')),
Tab(text: '交易记录'), Tab(text: context.t('tradingPage.tradeRecords')),
], ],
), ),
), ),
@ -61,9 +62,9 @@ class _TradingPageState extends State<TradingPage>
separatorBuilder: (_, __) => const SizedBox(height: 12), separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final statuses = [ final statuses = [
StatusTags.onSale(), StatusTags.onSale(context: context),
StatusTags.completed(), StatusTags.completed(context: context),
StatusTags.cancelled(), StatusTags.cancelled(context: context),
]; ];
return Container( return Container(
padding: AppSpacing.cardPadding, padding: AppSpacing.cardPadding,
@ -96,7 +97,7 @@ class _TradingPageState extends State<TradingPage>
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
children: [ children: [
Text('挂单价 ', style: AppTypography.caption), Text('${context.t('tradingPage.listPrice')} ', style: AppTypography.caption),
Text('\$${[21.25, 42.50, 68.00][index]}', Text('\$${[21.25, 42.50, 68.00][index]}',
style: AppTypography.priceSmall.copyWith(fontSize: 14)), style: AppTypography.priceSmall.copyWith(fontSize: 14)),
], ],
@ -117,12 +118,12 @@ class _TradingPageState extends State<TradingPage>
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('挂单时间: 2026/02/${9 - index}', Text('${context.t('tradingPage.listTime')}: 2026/02/${9 - index}',
style: AppTypography.caption), style: AppTypography.caption),
if (index == 0) if (index == 0)
GestureDetector( GestureDetector(
onTap: () {}, onTap: () {},
child: Text('撤单', style: AppTypography.labelSmall.copyWith( child: Text(context.t('tradingPage.cancelOrder'), style: AppTypography.labelSmall.copyWith(
color: AppColors.error, color: AppColors.error,
)), )),
), ),
@ -171,11 +172,11 @@ class _TradingPageState extends State<TradingPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
isBuy ? '买入' : '卖出', isBuy ? context.t('tradingPage.buy') : context.t('tradingPage.sell'),
style: AppTypography.labelMedium, style: AppTypography.labelMedium,
), ),
Text( Text(
'品牌 ${index + 1} 礼品卡', 'Brand ${index + 1} Gift Card',
style: AppTypography.caption, style: AppTypography.caption,
), ),
], ],

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -21,7 +22,7 @@ class _TransferPageState extends State<TransferPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('转赠给好友')), appBar: AppBar(title: Text(context.t('transfer.title'))),
body: Column( body: Column(
children: [ children: [
// Coupon Info // Coupon Info
@ -50,7 +51,7 @@ class _TransferPageState extends State<TransferPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('星巴克 \$25 礼品卡', style: AppTypography.labelMedium), Text('星巴克 \$25 礼品卡', style: AppTypography.labelMedium),
Text('面值 \$25.00', style: AppTypography.bodySmall), Text('${context.t('couponDetail.faceValue')} \$25.00', style: AppTypography.bodySmall),
], ],
), ),
), ),
@ -63,9 +64,9 @@ class _TransferPageState extends State<TransferPage> {
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: '搜索好友(手机号/用户名)', hintText: context.t('transfer.searchFriend'),
prefixIcon: Icon(Icons.search_rounded), prefixIcon: const Icon(Icons.search_rounded),
), ),
), ),
), ),
@ -92,7 +93,7 @@ class _TransferPageState extends State<TransferPage> {
height: AppSpacing.buttonHeight, height: AppSpacing.buttonHeight,
child: ElevatedButton( child: ElevatedButton(
onPressed: _selectedFriend != null ? () => _showConfirm(context) : null, onPressed: _selectedFriend != null ? () => _showConfirm(context) : null,
child: Text(_selectedFriend != null ? '转赠给 $_selectedFriend' : '请选择好友'), child: Text(_selectedFriend != null ? '${context.t('transfer.confirmTransfer')} $_selectedFriend' : context.t('transfer.searchFriend')),
), ),
), ),
), ),
@ -150,18 +151,16 @@ class _TransferPageState extends State<TransferPage> {
children: [ children: [
const Icon(Icons.card_giftcard_rounded, color: AppColors.primary, size: 48), const Icon(Icons.card_giftcard_rounded, color: AppColors.primary, size: 48),
const SizedBox(height: 16), const SizedBox(height: 16),
Text('确认转赠', style: AppTypography.h2), Text(context.t('transfer.confirmTransfer'), style: AppTypography.h2),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('将 星巴克 \$25 礼品卡 转赠给 $_selectedFriend', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)), Text('星巴克 \$25 礼品卡 -> $_selectedFriend', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)),
const SizedBox(height: 8),
Text('转赠后您将不再持有此券', style: AppTypography.caption.copyWith(color: AppColors.warning)),
const SizedBox(height: 24), const SizedBox(height: 24),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
child: const Text('取消'), child: Text(context.t('transfer.cancel')),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@ -171,7 +170,7 @@ class _TransferPageState extends State<TransferPage> {
Navigator.pop(ctx); Navigator.pop(ctx);
_showSuccess(context); _showSuccess(context);
}, },
child: const Text('确认转赠'), child: Text(context.t('transfer.confirmTransfer')),
), ),
), ),
], ],
@ -191,9 +190,9 @@ class _TransferPageState extends State<TransferPage> {
children: [ children: [
const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 56), const Icon(Icons.check_circle_rounded, color: AppColors.success, size: 56),
const SizedBox(height: 16), const SizedBox(height: 16),
Text('转赠成功', style: AppTypography.h2), Text(context.t('transfer.success'), style: AppTypography.h2),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('$_selectedFriend 已收到您的券', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)), Text('$_selectedFriend', style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary)),
], ],
), ),
actions: [ actions: [
@ -202,7 +201,7 @@ class _TransferPageState extends State<TransferPage> {
Navigator.pop(ctx); Navigator.pop(ctx);
Navigator.pop(context); Navigator.pop(context);
}, },
child: const Text('确定'), child: Text(context.t('transfer.confirm')),
), ),
], ],
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -22,7 +23,7 @@ class _DepositPageState extends State<DepositPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('充值')), appBar: AppBar(title: Text(context.t('deposit.title'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
@ -38,7 +39,7 @@ class _DepositPageState extends State<DepositPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('当前余额', style: AppTypography.bodySmall.copyWith(color: Colors.white70)), Text(context.t('deposit.currentBalance'), style: AppTypography.bodySmall.copyWith(color: Colors.white70)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('\$128.50', style: AppTypography.displayLarge.copyWith(color: Colors.white)), Text('\$128.50', style: AppTypography.displayLarge.copyWith(color: Colors.white)),
], ],
@ -46,7 +47,7 @@ class _DepositPageState extends State<DepositPage> {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text('充值金额', style: AppTypography.h3), Text(context.t('deposit.amount'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
// Preset Amounts // Preset Amounts
@ -90,8 +91,8 @@ class _DepositPageState extends State<DepositPage> {
TextField( TextField(
controller: _amountController, controller: _amountController,
keyboardType: const TextInputType.numberWithOptions(decimal: true), keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration( decoration: InputDecoration(
labelText: '自定义金额', labelText: context.t('deposit.custom'),
prefixText: '\$ ', prefixText: '\$ ',
), ),
onChanged: (_) => setState(() => _selectedPreset = null), onChanged: (_) => setState(() => _selectedPreset = null),
@ -99,7 +100,7 @@ class _DepositPageState extends State<DepositPage> {
const SizedBox(height: 24), const SizedBox(height: 24),
// Payment Method // Payment Method
Text('支付方式', style: AppTypography.h3), Text(context.t('deposit.paymentMethod'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildPaymentOption('Visa •••• 4242', Icons.credit_card_rounded, true), _buildPaymentOption('Visa •••• 4242', Icons.credit_card_rounded, true),
_buildPaymentOption('Apple Pay', Icons.apple_rounded, false), _buildPaymentOption('Apple Pay', Icons.apple_rounded, false),
@ -112,7 +113,7 @@ class _DepositPageState extends State<DepositPage> {
height: AppSpacing.buttonHeight, height: AppSpacing.buttonHeight,
child: ElevatedButton( child: ElevatedButton(
onPressed: _amountController.text.isNotEmpty ? () {} : null, onPressed: _amountController.text.isNotEmpty ? () {} : null,
child: Text('充值 \$${_amountController.text.isNotEmpty ? _amountController.text : '0'}'), child: Text('${context.t('deposit.submit')} \$${_amountController.text.isNotEmpty ? _amountController.text : '0'}'),
), ),
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -16,30 +17,30 @@ class TransactionRecordsPage extends StatelessWidget {
length: 4, length: 4,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('交易记录'), title: Text(context.t('txRecords.title')),
bottom: const TabBar( bottom: TabBar(
isScrollable: true, isScrollable: true,
tabs: [ tabs: [
Tab(text: '全部'), Tab(text: context.t('txRecords.all')),
Tab(text: '购买'), Tab(text: context.t('txRecords.buy')),
Tab(text: '出售'), Tab(text: context.t('txRecords.sell')),
Tab(text: '转赠'), Tab(text: context.t('txRecords.transfer')),
], ],
), ),
), ),
body: TabBarView( body: TabBarView(
children: [ children: [
_buildList(_allRecords), _buildList(context, _allRecords),
_buildList(_allRecords.where((r) => r.type == '购买').toList()), _buildList(context, _allRecords.where((r) => r.type == 'buy').toList()),
_buildList(_allRecords.where((r) => r.type == '出售').toList()), _buildList(context, _allRecords.where((r) => r.type == 'sell').toList()),
_buildList(_allRecords.where((r) => r.type == '转赠').toList()), _buildList(context, _allRecords.where((r) => r.type == 'transfer').toList()),
], ],
), ),
), ),
); );
} }
Widget _buildList(List<_TxRecord> records) { Widget _buildList(BuildContext context, List<_TxRecord> records) {
if (records.isEmpty) { if (records.isEmpty) {
return Center( return Center(
child: Column( child: Column(
@ -47,7 +48,7 @@ class TransactionRecordsPage extends StatelessWidget {
children: [ children: [
const Icon(Icons.receipt_long_rounded, size: 48, color: AppColors.textTertiary), const Icon(Icons.receipt_long_rounded, size: 48, color: AppColors.textTertiary),
const SizedBox(height: 12), const SizedBox(height: 12),
Text('暂无记录', style: AppTypography.bodyMedium.copyWith(color: AppColors.textTertiary)), Text(context.t('txRecords.noRecords'), style: AppTypography.bodyMedium.copyWith(color: AppColors.textTertiary)),
], ],
), ),
); );
@ -124,9 +125,9 @@ class _TxRecord {
} }
const _allRecords = [ const _allRecords = [
_TxRecord(type: '购买', title: '购买 星巴克 \$25 礼品卡', subtitle: '订单号 GNX20260210001', amount: '-\$21.25', time: '今天 14:32', icon: Icons.shopping_cart_rounded, color: AppColors.primary), _TxRecord(type: 'buy', title: 'Starbucks \$25 Gift Card', subtitle: 'GNX20260210001', amount: '-\$21.25', time: '14:32', icon: Icons.shopping_cart_rounded, color: AppColors.primary),
_TxRecord(type: '出售', title: '出售 Amazon \$100 购物券', subtitle: '订单号 GNX20260210002', amount: '+\$92.00', time: '今天 12:15', icon: Icons.sell_rounded, color: AppColors.success), _TxRecord(type: 'sell', title: 'Amazon \$100 Voucher', subtitle: 'GNX20260210002', amount: '+\$92.00', time: '12:15', icon: Icons.sell_rounded, color: AppColors.success),
_TxRecord(type: '转赠', title: '转赠给 Alice', subtitle: 'Nike \$80 运动券', amount: '\$0', time: '昨天 18:45', icon: Icons.card_giftcard_rounded, color: AppColors.info), _TxRecord(type: 'transfer', title: 'Nike \$80 Voucher -> Alice', subtitle: 'GNX20260209003', amount: '\$0', time: '18:45', icon: Icons.card_giftcard_rounded, color: AppColors.info),
_TxRecord(type: '购买', title: '购买 Target \$30 折扣券', subtitle: '订单号 GNX20260209001', amount: '-\$24.00', time: '昨天 10:20', icon: Icons.shopping_cart_rounded, color: AppColors.primary), _TxRecord(type: 'buy', title: 'Target \$30 Voucher', subtitle: 'GNX20260209001', amount: '-\$24.00', time: '10:20', icon: Icons.shopping_cart_rounded, color: AppColors.primary),
_TxRecord(type: '出售', title: '出售 Walmart \$50 生活券', subtitle: '订单号 GNX20260208003', amount: '+\$46.50', time: '2天前', icon: Icons.sell_rounded, color: AppColors.success), _TxRecord(type: 'sell', title: 'Walmart \$50 Voucher', subtitle: 'GNX20260208003', amount: '+\$46.50', time: '02/08', icon: Icons.sell_rounded, color: AppColors.success),
]; ];

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -15,13 +16,13 @@ class WalletPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('我的余额'), title: Text(context.t('wallet.myBalance')),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
// Balance Card // Balance Card
_buildBalanceCard(), _buildBalanceCard(context),
// Quick Actions // Quick Actions
Padding( Padding(
@ -30,7 +31,7 @@ class WalletPage extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: GenexButton( child: GenexButton(
label: '充值', label: context.t('wallet.deposit'),
icon: Icons.add_rounded, icon: Icons.add_rounded,
variant: GenexButtonVariant.primary, variant: GenexButtonVariant.primary,
onPressed: () { onPressed: () {
@ -41,7 +42,7 @@ class WalletPage extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: GenexButton( child: GenexButton(
label: '提现', label: context.t('wallet.withdraw'),
icon: Icons.account_balance_rounded, icon: Icons.account_balance_rounded,
variant: GenexButtonVariant.outline, variant: GenexButtonVariant.outline,
onPressed: () { onPressed: () {
@ -60,14 +61,14 @@ class WalletPage extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('交易记录', style: AppTypography.h3), Text(context.t('wallet.records'), style: AppTypography.h3),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
Navigator.pushNamed(context, '/wallet/records'); Navigator.pushNamed(context, '/wallet/records');
}, },
child: Row( child: Row(
children: [ children: [
Text('筛选', style: AppTypography.labelSmall.copyWith( Text(context.t('wallet.filter'), style: AppTypography.labelSmall.copyWith(
color: AppColors.textTertiary, color: AppColors.textTertiary,
)), )),
const Icon(Icons.filter_list_rounded, size: 16, const Icon(Icons.filter_list_rounded, size: 16,
@ -81,7 +82,7 @@ class WalletPage extends StatelessWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
// Transaction List // Transaction List
_buildTransactionList(), _buildTransactionList(context),
const SizedBox(height: 80), const SizedBox(height: 80),
], ],
@ -90,7 +91,7 @@ class WalletPage extends StatelessWidget {
); );
} }
Widget _buildBalanceCard() { Widget _buildBalanceCard(BuildContext context) {
return Container( return Container(
margin: const EdgeInsets.fromLTRB(20, 16, 20, 16), margin: const EdgeInsets.fromLTRB(20, 16, 20, 16),
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
@ -102,7 +103,7 @@ class WalletPage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('总余额', style: AppTypography.bodySmall.copyWith( Text(context.t('wallet.totalBalance'), style: AppTypography.bodySmall.copyWith(
color: Colors.white70, color: Colors.white70,
)), )),
const SizedBox(height: 8), const SizedBox(height: 8),
@ -116,9 +117,9 @@ class WalletPage extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
Row( Row(
children: [ children: [
_balanceItem('可提现', '\$1,034.56'), _balanceItem(context.t('wallet.withdrawable'), '\$1,034.56'),
const SizedBox(width: 32), const SizedBox(width: 32),
_balanceItem('冻结中', '\$200.00'), _balanceItem(context.t('wallet.frozen'), '\$200.00'),
], ],
), ),
], ],
@ -137,14 +138,14 @@ class WalletPage extends StatelessWidget {
); );
} }
Widget _buildTransactionList() { Widget _buildTransactionList(BuildContext context) {
final transactions = [ final transactions = [
('买入 星巴克 \$25 礼品卡', '-\$21.25', Icons.shopping_cart_rounded, AppColors.textPrimary, '今天 14:32'), ('${context.t('wallet.buyIn')} 星巴克 \$25 礼品卡', '-\$21.25', Icons.shopping_cart_rounded, AppColors.textPrimary, '14:32'),
('卖出 Amazon \$50 购物券', '+\$42.50', Icons.sell_rounded, AppColors.success, '今天 10:15'), ('${context.t('wallet.sellOut')} Amazon \$50 购物券', '+\$42.50', Icons.sell_rounded, AppColors.success, '10:15'),
('充值', '+\$500.00', Icons.add_circle_outline_rounded, AppColors.info, '昨天 09:20'), (context.t('wallet.deposit'), '+\$500.00', Icons.add_circle_outline_rounded, AppColors.info, '09:20'),
('转赠 Target 券', '-\$30.00', Icons.card_giftcard_rounded, AppColors.textPrimary, '02/07 16:45'), ('${context.t('wallet.giftTransfer')} Target 券', '-\$30.00', Icons.card_giftcard_rounded, AppColors.textPrimary, '16:45'),
('核销 Nike 运动券', '使用', Icons.check_circle_outline_rounded, AppColors.success, '02/06 12:00'), ('${context.t('wallet.redeemUse')} Nike 运动券', '-', Icons.check_circle_outline_rounded, AppColors.success, '12:00'),
('提现', '-\$200.00', Icons.account_balance_rounded, AppColors.textPrimary, '02/05 08:30'), (context.t('wallet.withdraw'), '-\$200.00', Icons.account_balance_rounded, AppColors.textPrimary, '08:30'),
]; ];
return ListView.separated( return ListView.separated(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../app/i18n/app_localizations.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';
@ -25,20 +26,20 @@ class _WithdrawPageState extends State<WithdrawPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('提现')), appBar: AppBar(title: Text(context.t('withdraw.title'))),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Balance // Balance
Text('可提现余额', style: AppTypography.bodySmall), Text(context.t('withdraw.availableBalance'), style: AppTypography.bodySmall),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('\$${_balance.toStringAsFixed(2)}', style: AppTypography.displayMedium), Text('\$${_balance.toStringAsFixed(2)}', style: AppTypography.displayMedium),
const SizedBox(height: 24), const SizedBox(height: 24),
// Amount Input // Amount Input
Text('提现金额', style: AppTypography.h3), Text(context.t('withdraw.amount'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
TextField( TextField(
controller: _amountController, controller: _amountController,
@ -50,7 +51,7 @@ class _WithdrawPageState extends State<WithdrawPage> {
_amountController.text = _balance.toStringAsFixed(2); _amountController.text = _balance.toStringAsFixed(2);
setState(() {}); setState(() {});
}, },
child: const Text('全部'), child: Text(context.t('withdraw.all')),
), ),
), ),
style: AppTypography.priceLarge, style: AppTypography.priceLarge,
@ -59,7 +60,7 @@ class _WithdrawPageState extends State<WithdrawPage> {
const SizedBox(height: 24), const SizedBox(height: 24),
// Withdraw To // Withdraw To
Text('提现到', style: AppTypography.h3), Text(context.t('withdraw.to'), style: AppTypography.h3),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
@ -77,7 +78,7 @@ class _WithdrawPageState extends State<WithdrawPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Bank of America •••• 6789', style: AppTypography.labelMedium), Text('Bank of America •••• 6789', style: AppTypography.labelMedium),
Text('储蓄账户', style: AppTypography.caption), Text(context.t('withdraw.savingsAccount'), style: AppTypography.caption),
], ],
), ),
), ),
@ -97,16 +98,16 @@ class _WithdrawPageState extends State<WithdrawPage> {
), ),
child: Column( child: Column(
children: [ children: [
_buildRow('提现金额', '\$${_amount.toStringAsFixed(2)}'), _buildRow(context.t('withdraw.amount'), '\$${_amount.toStringAsFixed(2)}'),
_buildRow('手续费 (0.5%)', '-\$${_fee.toStringAsFixed(2)}'), _buildRow(context.t('withdraw.fee'), '-\$${_fee.toStringAsFixed(2)}'),
const Divider(height: 16), const Divider(height: 16),
_buildRow('实际到账', '\$${_receive.toStringAsFixed(2)}', bold: true), _buildRow(context.t('withdraw.actualReceive'), '\$${_receive.toStringAsFixed(2)}', bold: true),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
children: [ children: [
const Icon(Icons.schedule_rounded, size: 14, color: AppColors.textTertiary), const Icon(Icons.schedule_rounded, size: 14, color: AppColors.textTertiary),
const SizedBox(width: 4), const SizedBox(width: 4),
Text('预计 1-2 个工作日到账', style: AppTypography.caption), Text(context.t('withdraw.estimateTime'), style: AppTypography.caption),
], ],
), ),
], ],
@ -122,7 +123,7 @@ class _WithdrawPageState extends State<WithdrawPage> {
height: AppSpacing.buttonHeight, height: AppSpacing.buttonHeight,
child: ElevatedButton( child: ElevatedButton(
onPressed: _amount > 0 && _amount <= _balance ? () {} : null, onPressed: _amount > 0 && _amount <= _balance ? () {} : null,
child: Text('确认提现 \$${_amount.toStringAsFixed(2)}'), child: Text('${context.t('withdraw.submit')} \$${_amount.toStringAsFixed(2)}'),
), ),
), ),
), ),

View File

@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'app/i18n/app_localizations.dart';
import 'app/theme/app_theme.dart'; import 'app/theme/app_theme.dart';
import 'app/main_shell.dart'; import 'app/main_shell.dart';
import 'features/auth/presentation/pages/login_page.dart'; import 'features/auth/presentation/pages/login_page.dart';
@ -45,6 +47,32 @@ class GenexConsumerApp extends StatelessWidget {
title: 'Genex', title: 'Genex',
theme: AppTheme.light, theme: AppTheme.light,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
supportedLocales: const [
Locale('zh', 'CN'),
Locale('zh', 'TW'),
Locale('en'),
Locale('ja'),
],
localizationsDelegates: const [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
localeResolutionCallback: (locale, supportedLocales) {
for (final supported in supportedLocales) {
if (supported.languageCode == locale?.languageCode &&
supported.countryCode == locale?.countryCode) {
return supported;
}
}
for (final supported in supportedLocales) {
if (supported.languageCode == locale?.languageCode) {
return supported;
}
}
return const Locale('zh', 'CN');
},
initialRoute: '/', initialRoute: '/',
onGenerateRoute: _generateRoute, onGenerateRoute: _generateRoute,
); );

Some files were not shown because too many files have changed in this diff Show More