gcx/frontend/admin-app/lib/features/settings/presentation/pages/settings_page.dart

329 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../../../../app/theme/app_colors.dart';
import '../../../../app/router.dart';
import '../../../../app/i18n/app_localizations.dart';
import '../../../../core/updater/update_service.dart';
import '../../../../core/services/issuer_service.dart';
import '../../../../core/services/auth_service.dart';
import '../../../../core/network/api_client.dart';
import '../../../../core/telemetry/telemetry_service.dart';
/// 发行方设置页面(我的)
///
/// 企业信息、门店管理、员工管理、专属客服、安全设置
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
String _appVersion = '';
bool _isLoadingProfile = true;
IssuerProfile? _profile;
bool _isLoggingOut = false;
final _issuerService = IssuerService();
final _authService = AuthService();
@override
void initState() {
super.initState();
_loadVersion();
_loadProfile();
}
Future<void> _loadVersion() async {
final info = await PackageInfo.fromPlatform();
if (mounted) {
setState(() => _appVersion = 'v${info.version}+${info.buildNumber}');
}
}
Future<void> _loadProfile() async {
try {
final profile = await _issuerService.getProfile();
if (!mounted) return;
setState(() {
_profile = profile;
_isLoadingProfile = false;
});
} catch (e) {
debugPrint('[SettingsPage] loadProfile error: $e');
if (!mounted) return;
setState(() => _isLoadingProfile = false);
}
}
Future<void> _handleLogout() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(context.t('settings_confirm_logout')),
content: Text(context.t('settings_confirm_logout_desc')),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(context.t('cancel'))),
ElevatedButton(
onPressed: () => Navigator.pop(ctx, true),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.error),
child: Text(context.t('confirm')),
),
],
),
);
if (confirmed != true) return;
setState(() => _isLoggingOut = true);
try {
await _authService.logout();
} catch (e) {
debugPrint('[SettingsPage] logout error: $e');
}
// 遥测:退出登录时清除 userId 和 token
if (TelemetryService().isInitialized) {
TelemetryService().clearUserId();
TelemetryService().clearAccessToken();
}
ApiClient.instance.setToken(null);
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(context, AppRouter.login, (_) => false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(context.t('settings_title'))),
body: SingleChildScrollView(
child: Column(
children: [
// Profile Card
_buildProfileCard(context),
// Tier & Benefits
_buildTierCard(context),
const SizedBox(height: 8),
// Menu Groups
_buildMenuGroup(context.t('settings_group_company'), [
_MenuItem(context.t('settings_company_info'), Icons.business_rounded, () {
// TODO: Navigate to company info page when available
}),
_MenuItem(context.t('settings_store_mgmt'), Icons.store_rounded, () {
Navigator.pushNamed(context, AppRouter.storeManagement);
}),
_MenuItem(context.t('settings_employee_mgmt'), Icons.people_rounded, () {
Navigator.pushNamed(context, AppRouter.storeManagement);
}),
_MenuItem(context.t('settings_permissions'), Icons.admin_panel_settings_rounded, () {
// TODO: Navigate to permissions page when available
}),
]),
_buildMenuGroup(context.t('settings_group_support'), [
_MenuItem(context.t('settings_ai_assistant'), Icons.auto_awesome_rounded, () {
Navigator.pushNamed(context, AppRouter.aiAgent);
}),
_MenuItem(context.t('settings_customer_service'), Icons.headset_mic_rounded, () {
// TODO: Navigate to customer service page when available
}),
_MenuItem(context.t('settings_help_center'), Icons.help_outline_rounded, () {
// TODO: Navigate to help center page when available
}),
_MenuItem(context.t('settings_feedback'), Icons.feedback_rounded, () {
// TODO: Navigate to feedback page when available
}),
]),
_buildMenuGroup(context.t('settings_group_security'), [
_MenuItem(context.t('settings_change_password'), Icons.lock_outline_rounded, () {
// TODO: Navigate to change password page when available
}),
_MenuItem(context.t('settings_operation_log'), Icons.history_rounded, () {
// TODO: Navigate to operation log page when available
}),
_MenuItem(
'${context.t('settings_about')}${_appVersion.isNotEmpty ? ' $_appVersion' : ''}',
Icons.info_outline_rounded,
() => UpdateService().manualCheckUpdate(context),
),
]),
// Logout
Padding(
padding: const EdgeInsets.all(20),
child: SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: _isLoggingOut ? null : _handleLogout,
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.error,
side: const BorderSide(color: AppColors.error),
),
child: _isLoggingOut
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(context.t('settings_logout')),
),
),
),
],
),
),
);
}
Widget _buildProfileCard(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
color: AppColors.surface,
child: Row(
children: [
_profile?.logoUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(14),
child: Image.network(
_profile!.logoUrl!,
width: 56,
height: 56,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => ClipRRect(
borderRadius: BorderRadius.circular(14),
child: Image.asset('assets/images/logo_icon.png', width: 56, height: 56),
),
),
)
: ClipRRect(
borderRadius: BorderRadius.circular(14),
child: Image.asset(
'assets/images/logo_icon.png',
width: 56,
height: 56,
),
),
const SizedBox(width: 14),
Expanded(
child: _isLoadingProfile
? const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
height: 16,
child: LinearProgressIndicator(),
),
SizedBox(height: 8),
SizedBox(
width: 80,
height: 12,
child: LinearProgressIndicator(),
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_profile?.companyName ?? '--',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
),
const SizedBox(height: 4),
Text(
_profile?.contactEmail != null
? '${context.t('settings_admin')}${_profile!.contactEmail}'
: _profile?.contactPhone != null
? '${context.t('settings_admin')}${_profile!.contactPhone}'
: context.t('settings_admin'),
style: const TextStyle(fontSize: 13, color: AppColors.textSecondary),
),
],
),
),
const Icon(Icons.chevron_right_rounded, color: AppColors.textTertiary),
],
),
);
}
Widget _buildTierCard(BuildContext context) {
return Container(
margin: const EdgeInsets.fromLTRB(20, 12, 20, 0),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFFFF7E6), Color(0xFFFFECC7)],
),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Icon(Icons.star_rounded, color: AppColors.tierGold, size: 28),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_profile?.creditRating != null
? '${_profile!.creditRating} ${context.t('settings_gold_issuer')}'
: context.t('settings_gold_issuer'),
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: AppColors.tierGold),
),
Text(context.t('settings_tier_benefits'), style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)),
],
),
),
TextButton(
onPressed: () {
Navigator.pushNamed(context, AppRouter.credit);
},
child: Text(context.t('settings_upgrade'), style: const TextStyle(color: AppColors.tierGold)),
),
],
),
);
}
Widget _buildMenuGroup(String title, List<_MenuItem> items) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 8),
child: Text(title, style: const TextStyle(fontSize: 13, color: AppColors.textTertiary, fontWeight: FontWeight.w500)),
),
Container(
color: AppColors.surface,
child: Column(
children: items.map((item) {
return ListTile(
leading: Icon(item.icon, color: AppColors.textSecondary, size: 22),
title: Text(item.title, style: const TextStyle(fontSize: 15)),
trailing: const Icon(Icons.chevron_right_rounded, size: 20, color: AppColors.textTertiary),
onTap: item.onTap,
);
}).toList(),
),
),
],
);
}
}
class _MenuItem {
final String title;
final IconData icon;
final VoidCallback onTap;
const _MenuItem(this.title, this.icon, this.onTap);
}