This commit is contained in:
2026-03-22 02:14:55 +08:00
parent faf00ec231
commit 0e95890d68
380 changed files with 2437 additions and 40937 deletions

View 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)';
}

View File

@@ -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 {

View File

@@ -71,7 +71,7 @@ class AppTheme {
color: AppColors.border,
thickness: 1,
),
cardTheme: CardTheme(
cardTheme: CardThemeData(
color: AppColors.cardBackground,
elevation: 0,
shape: RoundedRectangleBorder(

View File

@@ -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();
},
),
);
},
),
);
}

View 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('立即注册'),
),
],
),
],
),
),
),
),
),
);
}
}

View File

@@ -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, '安全设置', () {

View File

@@ -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 ?? '',
),