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:
parent
5bc1cbe4d8
commit
3cdb6a5eb9
File diff suppressed because it is too large
Load Diff
|
|
@ -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'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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}%'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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.25(8.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')),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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'))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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={{
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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={{
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,000的交易自动生成CTR,当前无待处理项</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>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -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: '审计中' },
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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' }}>
|
||||||
|
|
|
||||||
|
|
@ -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' }}>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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' }}>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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分钟前' },
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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)',
|
||||||
|
|
|
||||||
|
|
@ -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' }}>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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+店舗',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.',
|
||||||
|
};
|
||||||
|
|
@ -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': '受取ったクーポンは自動的にウォレットに保存されます。ホーム画面のウォレットから確認・管理できます。',
|
||||||
|
};
|
||||||
|
|
@ -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': '接收的券将自动存入你的钱包,可在首页钱包中查看和管理。',
|
||||||
|
};
|
||||||
|
|
@ -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': '接收的券將自動存入你的錢包,可在首頁錢包中查看和管理。',
|
||||||
|
};
|
||||||
|
|
@ -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')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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.25(8.5折),信用AAA\n2. Amazon \$100 购物券 - 当前售价 \$85(8.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.'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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'), '', () {}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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.50(9折),此价格成交概率最高',
|
'${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'))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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')),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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'}'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)}'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue