youhua
This commit is contained in:
38
flutter_monisuo/lib/core/network/api_exception.dart
Normal file
38
flutter_monisuo/lib/core/network/api_exception.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
/// API 异常类
|
||||
class ApiException implements Exception {
|
||||
final String message;
|
||||
final String code;
|
||||
final int? statusCode;
|
||||
|
||||
ApiException({
|
||||
required this.message,
|
||||
required this.code,
|
||||
this.statusCode,
|
||||
});
|
||||
|
||||
factory ApiException.unauthorized([String? message]) {
|
||||
return ApiException(
|
||||
message: message ?? '未授权',
|
||||
code: '0002',
|
||||
statusCode: 401,
|
||||
);
|
||||
}
|
||||
|
||||
factory ApiException.networkError([String? message]) {
|
||||
return ApiException(
|
||||
message: message ?? '网络错误',
|
||||
code: 'NETWORK_ERROR',
|
||||
);
|
||||
}
|
||||
|
||||
factory ApiException.serverError([String? message]) {
|
||||
return ApiException(
|
||||
message: message ?? '服务器错误',
|
||||
code: 'SERVER_ERROR',
|
||||
statusCode: 500,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'ApiException: $message (code: $code)';
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class ApiResponse<T> {
|
||||
if (fromJsonT != null && data != null) {
|
||||
return ApiResponse.success(fromJsonT(data), msg);
|
||||
}
|
||||
return ApiResponse.success(data as T?, msg);
|
||||
return ApiResponse.success(data as T, msg);
|
||||
} else if (code == '0002') {
|
||||
return ApiResponse.unauthorized(msg);
|
||||
} else {
|
||||
|
||||
@@ -71,7 +71,7 @@ class AppTheme {
|
||||
color: AppColors.border,
|
||||
thickness: 1,
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
cardTheme: CardThemeData(
|
||||
color: AppColors.cardBackground,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'core/constants/app_colors.dart';
|
||||
import 'core/network/dio_client.dart';
|
||||
import 'core/storage/local_storage.dart';
|
||||
import 'core/theme/app_theme.dart';
|
||||
import 'data/services/user_service.dart';
|
||||
import 'data/services/market_service.dart';
|
||||
import 'data/services/trade_service.dart';
|
||||
@@ -20,6 +20,9 @@ import 'ui/pages/main/main_page.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// 禁用 Provider 类型检查
|
||||
Provider.debugCheckInvalidValueType = null;
|
||||
|
||||
// 初始化本地存储
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await LocalStorage.init();
|
||||
@@ -37,59 +40,79 @@ class MyApp extends StatelessWidget {
|
||||
// 服务
|
||||
Provider<DioClient>(create: (_) => DioClient()),
|
||||
ProxyProvider<DioClient, UserService>(
|
||||
create: (_) => throw UnimplementedError(),
|
||||
update: (_, client) => UserService(client),
|
||||
create: (_) => UserService(DioClient()),
|
||||
update: (_, client, previous) => previous ?? UserService(client),
|
||||
),
|
||||
ProxyProvider<DioClient, MarketService>(
|
||||
create: (_) => throw UnimplementedError(),
|
||||
update: (_, client) => MarketService(client),
|
||||
create: (_) => MarketService(DioClient()),
|
||||
update: (_, client, previous) => previous ?? MarketService(client),
|
||||
),
|
||||
ProxyProvider<DioClient, TradeService>(
|
||||
create: (_) => throw UnimplementedError(),
|
||||
update: (_, client) => TradeService(client),
|
||||
create: (_) => TradeService(DioClient()),
|
||||
update: (_, client, previous) => previous ?? TradeService(client),
|
||||
),
|
||||
ProxyProvider<DioClient, AssetService>(
|
||||
create: (_) => throw UnimplementedError(),
|
||||
update: (_, client) => AssetService(client),
|
||||
create: (_) => AssetService(DioClient()),
|
||||
update: (_, client, previous) => previous ?? AssetService(client),
|
||||
),
|
||||
ProxyProvider<DioClient, FundService>(
|
||||
create: (_) => throw UnimplementedError(),
|
||||
update: (_, client) => FundService(client),
|
||||
create: (_) => FundService(DioClient()),
|
||||
update: (_, client, previous) => previous ?? FundService(client),
|
||||
),
|
||||
// 状态管理
|
||||
ProxyProvider2<UserService, DioClient, AuthProvider>(
|
||||
create: (_) => throw UnimplementedError(),
|
||||
update: (_, userService, __) => AuthProvider(userService),
|
||||
create: (_) => AuthProvider(UserService(DioClient())),
|
||||
update: (_, userService, __, previous) =>
|
||||
previous ?? AuthProvider(userService),
|
||||
),
|
||||
ProxyProvider<MarketService, MarketProvider>(
|
||||
create: (_) => throw UnimplementedError(),
|
||||
update: (_, service) => MarketProvider(service),
|
||||
create: (_) => MarketProvider(MarketService(DioClient())),
|
||||
update: (_, service, previous) =>
|
||||
previous ?? MarketProvider(service),
|
||||
),
|
||||
ProxyProvider2<AssetService, FundService, AssetProvider>(
|
||||
create: (_) => throw UnimplementedError(),
|
||||
update: (_, assetService, fundService) => AssetProvider(assetService, fundService),
|
||||
create: (_) =>
|
||||
AssetProvider(AssetService(DioClient()), FundService(DioClient())),
|
||||
update: (_, assetService, fundService, previous) =>
|
||||
previous ?? AssetProvider(assetService, fundService),
|
||||
),
|
||||
],
|
||||
child: MaterialApp(
|
||||
title: '模拟所',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.darkTheme,
|
||||
home: Consumer<AuthProvider>(
|
||||
builder: (context, auth, _) {
|
||||
if (auth.isLoading) {
|
||||
return const Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(color: AppColors.primary),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (auth.isLoggedIn) {
|
||||
return const MainPage();
|
||||
}
|
||||
return const LoginPage();
|
||||
},
|
||||
child: ShadApp.custom(
|
||||
themeMode: ThemeMode.dark,
|
||||
darkTheme: ShadThemeData(
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: const ShadSlateColorScheme.dark(),
|
||||
),
|
||||
appBuilder: (context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: Theme.of(context),
|
||||
localizationsDelegates: const [
|
||||
GlobalShadLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
builder: (context, child) {
|
||||
return ShadAppBuilder(child: child!);
|
||||
},
|
||||
home: Consumer<AuthProvider>(
|
||||
builder: (context, auth, _) {
|
||||
if (auth.isLoading) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (auth.isLoggedIn) {
|
||||
return const MainPage();
|
||||
}
|
||||
return const LoginPage();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
164
flutter_monisuo/lib/ui/pages/auth/login_page_shadcn.dart
Normal file
164
flutter_monisuo/lib/ui/pages/auth/login_page_shadcn.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../providers/auth_provider.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final formKey = GlobalKey<ShadFormState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ShadForm(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Logo 和标题
|
||||
Icon(
|
||||
LucideIcons.trendingUp,
|
||||
size: 64,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'模拟所',
|
||||
style: theme.textTheme.h1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'虚拟货币模拟交易平台',
|
||||
style: theme.textTheme.muted,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
|
||||
// 用户名输入
|
||||
ShadInputFormField(
|
||||
id: 'username',
|
||||
label: const Text('用户名'),
|
||||
placeholder: const Text('请输入用户名'),
|
||||
leading: const Icon(LucideIcons.user),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '请输入用户名';
|
||||
}
|
||||
if (value.length < 3) {
|
||||
return '用户名至少 3 个字符';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 密码输入
|
||||
ShadInputFormField(
|
||||
id: 'password',
|
||||
label: const Text('密码'),
|
||||
placeholder: const Text('请输入密码'),
|
||||
obscureText: true,
|
||||
leading: const Icon(LucideIcons.lock),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '请输入密码';
|
||||
}
|
||||
if (value.length < 6) {
|
||||
return '密码至少 6 个字符';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 登录按钮
|
||||
Consumer<AuthProvider>(
|
||||
builder: (context, auth, _) {
|
||||
return ShadButton(
|
||||
onPressed: auth.isLoading
|
||||
? null
|
||||
: () async {
|
||||
if (formKey.currentState!.saveAndValidate()) {
|
||||
final values = formKey.currentState!.value;
|
||||
final success = await auth.login(
|
||||
values['username'],
|
||||
values['password'],
|
||||
);
|
||||
|
||||
if (!success && mounted) {
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (context) => ShadDialog.alert(
|
||||
title: const Text('登录失败'),
|
||||
description: Text(
|
||||
auth.error ?? '用户名或密码错误',
|
||||
),
|
||||
actions: [
|
||||
ShadButton(
|
||||
child: const Text('确定'),
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: auth.isLoading
|
||||
? const SizedBox.square(
|
||||
dimension: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Text('登录'),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 注册链接
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'还没有账号?',
|
||||
style: theme.textTheme.muted,
|
||||
),
|
||||
ShadButton.link(
|
||||
onPressed: () {
|
||||
// 跳转到注册页面
|
||||
// context.go('/register');
|
||||
},
|
||||
child: const Text('立即注册'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,7 @@ class _MinePageState extends State<MinePage> with AutomaticKeepAliveClientMixin
|
||||
_buildMenuItem(Icons.verified_user, '实名认证', () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('功能开发中')),
|
||||
);
|
||||
}),
|
||||
const Divider(color: AppColors.border, height: 1),
|
||||
_buildMenuItem(Icons.security, '安全设置', () {
|
||||
|
||||
@@ -252,7 +252,7 @@ class _TradePageState extends State<TradePage> with AutomaticKeepAliveClientMixi
|
||||
controller: _quantityController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
style: const TextStyle(color: AppColors.textPrimary),
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
labelText: '数量',
|
||||
suffixText: _selectedCoin?.code ?? '',
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user