合并代码

This commit is contained in:
chenshuainan
2026-09-09 16:15:05 +08:00
parent 592e576319
commit 7acf745295
24 changed files with 1864 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart';
import 'package:new_learn/core/api/auth/api.dart';
import 'package:new_learn/core/components/app_update_dialog.dart';
import 'package:new_learn/core/components/confirm_box.dart';
import 'package:new_learn/core/network/dio_client.dart';
import 'package:new_learn/core/providers/user_provider.dart';
import 'package:new_learn/core/storage/user_storage.dart';
import 'package:new_learn/core/version/app_version_service.dart';
/// ============================================================
/// 【SettingsPage】设置页 —— 对齐 Vue pages/myPackage/settings/index.vue
///
/// 三栏菜单:
/// 检查更新(右侧显示当前版本号,手动触发版本比对弹窗)
/// 关于APP (TODO: 跳转关于页)
/// 注销账号 (Vue 端受 isAuditMode 控制,Flutter 暂先显示)
///
/// 样式:白色圆角卡片 + 分隔线,和 my_page 的 _MenuCard 风格一致
/// ============================================================
class SettingsPage extends ConsumerStatefulWidget {
const SettingsPage({super.key});
@override
ConsumerState<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends ConsumerState<SettingsPage> {
String _version = '';
@override
void initState() {
super.initState();
_loadVersion();
}
Future<void> _loadVersion() async {
final service = AppVersionService.instance;
if (service.versionName.isEmpty) {
await service.init();
}
if (mounted) {
setState(() => _version = service.versionName);
}
}
Future<void> _handleCheckUpdate() async {
// 手动检查更新:复用 AppVersionService + AppUpdateDialog
try {
final service = AppVersionService.instance;
await service.init();
final update = await service.checkForUpdate();
if (!mounted) return;
if (update == null) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('已是最新版本')));
return;
}
await AppUpdateDialog.show(
context,
update,
isForce: service.isForceUpdate(),
);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('检查更新失败:$e')));
}
}
Future<void> _handleCloseAccount() async {
final ok = await showConfirmBox(
context,
title: '注销提醒',
content: '账号注销后,将会删除账号的全部数据,不可恢复。请确认是否注销账号?',
confirmText: '确定注销',
cancelText: '我再想想',
);
if (!ok) return;
try {
await UserApi.closeAccount();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('注销失败:$e')));
return;
}
// 清理本地登录态 → 路由守卫自动踢回登录页
DioClient.instance.accessToken = null;
await UserStorage.instance.clearAll();
if (!mounted) return;
ref.read(currentUserProvider.notifier).set(null);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F8FA),
appBar: AppBar(
title: const Text('设置'),
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.canPop() ? context.pop() : context.go('/my'),
),
),
body: ListView(
padding: EdgeInsets.all(12.w),
children: [
// 检查更新
_buildRow(
title: '检查更新',
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (_version.isNotEmpty)
Text(
'v$_version',
style: TextStyle(
fontSize: 12.sp,
color: const Color(0xFF687FAD),
),
),
SizedBox(width: 4.w),
const Icon(
Icons.chevron_right,
size: 22,
color: Color(0xFF7FB0F2),
),
],
),
onTap: _handleCheckUpdate,
),
_buildDivider(),
// 关于APP
_buildRow(
title: '关于APP',
trailing: const Icon(
Icons.chevron_right,
size: 22,
color: Color(0xFF7FB0F2),
),
onTap: () => context.push('/about-app'),
),
_buildDivider(),
// 注销账号
_buildRow(
title: '注销账号',
trailing: const Icon(
Icons.chevron_right,
size: 22,
color: Color(0xFF7FB0F2),
),
onTap: _handleCloseAccount,
),
],
),
);
}
Widget _buildDivider() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Container(height: 1, color: const Color(0xFFEDF3F7)),
);
}
Widget _buildRow({
required String title,
required Widget trailing,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
child: Container(
height: 52.h,
padding: EdgeInsets.symmetric(horizontal: 16.w),
color: Colors.white,
child: Row(
children: [
Text(
title,
style: TextStyle(fontSize: 16.sp, color: const Color(0xFF1D2B47)),
),
const Spacer(),
trailing,
],
),
),
);
}
}