329 lines
9.9 KiB
Dart
329 lines
9.9 KiB
Dart
import 'package:autosos_flutter/api/_api_client.dart';
|
||
import 'package:autosos_flutter/model/user.dart';
|
||
import 'package:autosos_flutter/provider/providers.dart';
|
||
import 'package:autosos_flutter/router/app_router.dart';
|
||
import 'package:autosos_flutter/util/sp_util.dart';
|
||
import 'package:autosos_flutter/widget/multi_state_view.dart';
|
||
import 'package:cached_network_image/cached_network_image.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
|
||
/// 我的中心(对应 Android MeFragment)
|
||
class MePage extends ConsumerStatefulWidget {
|
||
const MePage({super.key});
|
||
|
||
@override
|
||
ConsumerState<MePage> createState() => _MePageState();
|
||
}
|
||
|
||
class _MePageState extends ConsumerState<MePage> {
|
||
MultiState _state = MultiState.loading;
|
||
UserInfo? _user;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_load();
|
||
}
|
||
|
||
Future<void> _load() async {
|
||
setState(() => _state = MultiState.loading);
|
||
try {
|
||
final resp = await ref.read(userApiProvider).info();
|
||
if (!mounted) return;
|
||
if (!resp.isSuccess || resp.data == null) {
|
||
setState(() => _state = MultiState.error);
|
||
return;
|
||
}
|
||
ref.read(currentUserProvider.notifier).state = resp.data;
|
||
setState(() {
|
||
_user = resp.data;
|
||
_state = MultiState.success;
|
||
});
|
||
} catch (e) {
|
||
if (mounted) setState(() => _state = MultiState.error);
|
||
}
|
||
}
|
||
|
||
Future<void> _logout() async {
|
||
final ok = await showDialog<bool>(
|
||
context: context,
|
||
builder: (ctx) => AlertDialog(
|
||
title: const Text('退出登录'),
|
||
content: const Text('确定要退出当前账号吗?'),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(ctx, false),
|
||
child: const Text('取消'),
|
||
),
|
||
ElevatedButton(
|
||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||
onPressed: () => Navigator.pop(ctx, true),
|
||
child: const Text('退出'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
if (ok != true) return;
|
||
EasyLoading.show(status: '退出中…');
|
||
try {
|
||
await ref.read(userApiProvider).logout();
|
||
} catch (_) {}
|
||
// 清 token + sp
|
||
SPUtil().remove('accessToken');
|
||
SPUtil().remove('user');
|
||
ApiClient.instance.clearToken();
|
||
ref.read(accessTokenProvider.notifier).state = null;
|
||
ref.read(currentUserProvider.notifier).state = null;
|
||
EasyLoading.dismiss();
|
||
if (mounted) context.go(Routes.login);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: Colors.grey.shade100,
|
||
body: RefreshIndicator(
|
||
onRefresh: _load,
|
||
child: MultiStateView(
|
||
state: _state,
|
||
onRetry: _load,
|
||
child: _user == null
|
||
? const SizedBox.shrink()
|
||
: ListView(
|
||
children: [
|
||
_header(_user!),
|
||
const SizedBox(height: 8),
|
||
_statsRow(_user!),
|
||
const SizedBox(height: 8),
|
||
_menu(),
|
||
const SizedBox(height: 24),
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: OutlinedButton.icon(
|
||
onPressed: _logout,
|
||
icon: const Icon(Icons.logout, color: Colors.red),
|
||
label: const Text(
|
||
'退出登录',
|
||
style: TextStyle(color: Colors.red),
|
||
),
|
||
style: OutlinedButton.styleFrom(
|
||
side: const BorderSide(color: Colors.red),
|
||
minimumSize: const Size.fromHeight(44),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _header(UserInfo u) {
|
||
return Container(
|
||
padding: const EdgeInsets.fromLTRB(16, 36, 16, 16),
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
colors: [Colors.red.shade400, Colors.red.shade700],
|
||
),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
CircleAvatar(
|
||
radius: 32,
|
||
backgroundColor: Colors.white,
|
||
child: u.avatar != null && u.avatar!.isNotEmpty
|
||
? ClipOval(
|
||
child: CachedNetworkImage(
|
||
imageUrl: u.avatar!,
|
||
width: 60,
|
||
height: 60,
|
||
fit: BoxFit.cover,
|
||
),
|
||
)
|
||
: Text(
|
||
(u.realName?.isNotEmpty == true
|
||
? u.realName![0]
|
||
: (u.username?.isNotEmpty == true ? u.username![0] : '?'))
|
||
.toUpperCase(),
|
||
style: const TextStyle(
|
||
fontSize: 24,
|
||
color: Colors.red,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
u.realName ?? u.username ?? '—',
|
||
style: const TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Row(
|
||
children: [
|
||
Text(u.phone ?? '—',
|
||
style: const TextStyle(color: Colors.white70)),
|
||
const SizedBox(width: 8),
|
||
_badge(u.authStatus ?? 'none'),
|
||
],
|
||
),
|
||
if (u.company != null && u.company!.isNotEmpty) ...[
|
||
const SizedBox(height: 4),
|
||
Text(u.company!,
|
||
style: const TextStyle(color: Colors.white70)),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
IconButton(
|
||
onPressed: () => context.push(Routes.setting),
|
||
icon: const Icon(Icons.settings, color: Colors.white),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _badge(String status) {
|
||
Color bg;
|
||
String text;
|
||
switch (status) {
|
||
case 'personal':
|
||
bg = Colors.blue;
|
||
text = '个人认证';
|
||
break;
|
||
case 'enterprise':
|
||
bg = Colors.purple;
|
||
text = '企业认证';
|
||
break;
|
||
case 'driver':
|
||
bg = Colors.orange;
|
||
text = '司机认证';
|
||
break;
|
||
default:
|
||
bg = Colors.grey;
|
||
text = '未认证';
|
||
}
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||
decoration: BoxDecoration(
|
||
color: bg,
|
||
borderRadius: BorderRadius.circular(4),
|
||
),
|
||
child: Text(text,
|
||
style: const TextStyle(color: Colors.white, fontSize: 11)),
|
||
);
|
||
}
|
||
|
||
Widget _statsRow(UserInfo u) {
|
||
return Container(
|
||
color: Colors.white,
|
||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||
child: Row(
|
||
children: [
|
||
_stat('余额', '¥${u.balance?.toStringAsFixed(2) ?? "0.00"}'),
|
||
_divider(),
|
||
_stat('今日订单', '${u.todayOrders ?? 0}'),
|
||
_divider(),
|
||
_stat('累计订单', '${u.totalOrders ?? 0}'),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _stat(String label, String value) {
|
||
return Expanded(
|
||
child: Column(
|
||
children: [
|
||
Text(value,
|
||
style: const TextStyle(
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.red,
|
||
)),
|
||
const SizedBox(height: 2),
|
||
Text(label,
|
||
style: const TextStyle(fontSize: 12, color: Colors.grey)),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _divider() =>
|
||
Container(width: 1, height: 30, color: Colors.grey.shade300);
|
||
|
||
Widget _menu() {
|
||
return Container(
|
||
color: Colors.white,
|
||
child: Column(
|
||
children: [
|
||
_menuItem(Icons.account_balance_wallet, '提现', () {
|
||
context.push(Routes.withdrawal);
|
||
}),
|
||
_menuItem(Icons.history, '提现记录', () {
|
||
context.push(Routes.withdrawalRecord);
|
||
}),
|
||
_menuItem(Icons.payment, '充值', () {
|
||
context.push(Routes.recharge);
|
||
}),
|
||
_menuItem(Icons.list_alt, '金额明细', () {
|
||
context.push(Routes.amountList);
|
||
}),
|
||
_dividerLine(),
|
||
_menuItem(Icons.edit, '完善资料', () {
|
||
context.push(Routes.perfect);
|
||
}),
|
||
_menuItem(Icons.verified_user, '认证', () {
|
||
context.push(Routes.auth);
|
||
}),
|
||
_dividerLine(),
|
||
_menuItem(Icons.message, '消息中心', () {
|
||
context.push(Routes.messenger);
|
||
}),
|
||
_menuItem(Icons.bug_report, '问题反馈', () {
|
||
context.push(Routes.feedback);
|
||
}),
|
||
_menuItem(Icons.tune, '性能优化', () {
|
||
context.push(Routes.optimization);
|
||
}),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _menuItem(IconData icon, String label, VoidCallback onTap) {
|
||
return InkWell(
|
||
onTap: onTap,
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||
child: Row(
|
||
children: [
|
||
Icon(icon, color: Colors.red, size: 22),
|
||
const SizedBox(width: 12),
|
||
Expanded(child: Text(label, style: const TextStyle(fontSize: 15))),
|
||
const Icon(Icons.chevron_right, color: Colors.grey),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _dividerLine() => Divider(
|
||
height: 1,
|
||
thickness: 1,
|
||
color: Colors.grey.shade200,
|
||
indent: 16,
|
||
);
|
||
}
|