QinGang_interested/lib/pages/mine/mine_page.dart

539 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// file: mine_page.dart
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/home/userinfo_page.dart';
import 'package:qhd_prevention/pages/mine/face_ecognition_page.dart';
import 'package:qhd_prevention/pages/mine/mine_change_firm_page.dart';
import 'package:qhd_prevention/pages/mine/mine_feedback_page.dart';
import 'package:qhd_prevention/pages/mine/mine_set_pwd_page.dart';
import 'package:qhd_prevention/pages/mine/onboarding_full_page.dart';
import 'package:qhd_prevention/pages/user/full_userinfo_page.dart';
import 'package:qhd_prevention/pages/user/login_page.dart';
import 'package:qhd_prevention/services/SessionService.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'certificate/certificate_list_page.dart';
import 'package:qhd_prevention/common/route_service.dart';
import 'package:qhd_prevention/common/route_model.dart';
class MinePage extends StatefulWidget {
const MinePage({super.key, required this.isChooseFirm});
final bool isChooseFirm;
@override
State<MinePage> createState() => MinePageState();
}
class MinePageState extends State<MinePage> {
// 设置项状态
bool notificationsEnabled = false;
bool passwordChanged = false;
bool updateAvailable = false;
bool logoutSelected = false;
bool faceAuthentication = false;
bool scanAuthentication = false;
String name = '登录/注册';
String phone = '';
/// 配置UI 顺序/图标/对应后端 menuPerm(s)
/// perm 列表为空表示不受后端配置控制(始终显示)
final List<Map<String, dynamic>> _allSettings = [
{
'title': '我的信息',
'icon': 'assets/images/ico9.png',
'perms': ['my-center-My-Information'],
'action': 'userinfo',
},
{
'title': '修改密码',
'icon': 'assets/images/ico16.png',
'perms': ['my-center-Change-Password'],
'action': 'changePwd',
},
{
'title': '扫码入职',
'icon': 'assets/images/ico10.png',
// 如果你希望扫码入职也受后端控制,可以保留下面 perm否则置空 []
'perms': ['my-center-Scan-Code-Onboarding'],
'action': 'scanOnboarding',
},
{
'title': '人脸认证',
'icon': 'assets/images/ico11.png',
'perms': ['my-center-Face-Authentication'],
'action': 'face',
},
{
'title': '证书信息',
'icon': 'assets/images/ico12.png',
'perms': ['my-center-Certificate-Information'],
'action': 'certificate',
},
// {
// 'title': '版本更新',
// 'icon': 'assets/images/ico14.png',
// 'perms': ['my-center-Version-Update'],
// 'action': 'version',
// },
// {
// 'title': '关于我们',
// 'icon': 'assets/images/ico15.png',
// 'perms': ['my-center-About-Us'],
// 'action': 'about',
// },
{
'title': '切换账户',
'icon': 'assets/images/ico_switch.png',
// 切换账户仍只在 widget.isChooseFirm 为 true 时显示(参见构建函数)
'perms': ['my-center-Switch-Account'],
'action': 'changeAccount',
},
{
'title': '账户注销',
'icon': 'assets/images/ico_quit.png',
// 后端可能有多种命名,两者任一存在即显示
'perms': ['my-center-User-Logout', 'my-center-Logout'],
'action': 'logout',
},
];
// 当前可见项(与 _allSettings 顺序对应)
late List<bool> _visible;
void onRouteConfigLoaded() {
if (mounted) {
setState(() {
// 这里不直接修改业务逻辑仅触发重建update 会在 listener 中执行)
});
}
}
@override
void initState() {
super.initState();
_getUserInfo();
// 初始默认都显示,避免短时白屏/闪烁
_visible = List<bool>.filled(_allSettings.length, true);
// 监听路由变化来决定哪些设置项应显示
RouteService().addListener(_onRouteServiceUpdated);
// 尝试立即应用(如果 route 已加载)
WidgetsBinding.instance.addPostFrameCallback(
(_) => _updateVisibilityFromRoute(),
);
}
@override
void dispose() {
try {
RouteService().removeListener(_onRouteServiceUpdated);
} catch (_) {}
super.dispose();
}
void _onRouteServiceUpdated() {
_updateVisibilityFromRoute();
}
/// 根据 RouteService 的路由配置计算每个条目的可见性
void _updateVisibilityFromRoute() {
final rs = RouteService();
// 如果 mainTabs 为空,说明路由还没加载;保持当前 visible避免闪烁
if (rs.mainTabs.isEmpty) {
return;
}
final List<bool> next = List<bool>.filled(_allSettings.length, false);
for (int i = 0; i < _allSettings.length; i++) {
final perms = (_allSettings[i]['perms'] ?? []) as List<String>;
if (perms.isEmpty) {
// 无后端控制的项默认显示(比如你希望用户协议/隐私总是能见到)
next[i] = true;
continue;
}
// 只要找到任一 perm 在路由树中可见,即认为该项可见
bool anyFound = false;
for (final p in perms) {
final node = rs.findRouteByPerm(p);
if (node != null) {
// rs.findRouteByPerm 已经会考虑父节点 visible 的情况(按照 RouteService 实现)
// 这里再用 showFlag==1 做额外判断:当后端返回节点但 showFlag==0 (不显示) 则视为不可见
if (node.showFlag == 1 || node.visible) {
anyFound = true;
break;
}
}
}
next[i] = anyFound;
}
// 与当前 _visible 比较,只有变更时才 setState
if (!listEquals(next, _visible)) {
setState(() {
_visible = next;
});
}
}
// 获取用户信息
Future<void> _getUserInfo() async {
setState(() {
name = SessionService.instance.userData?.name ?? "登录/注册";
phone = SessionService.instance.userData?.phone ?? "";
});
// final accountId =
// SessionService.instance.accountId ??
// SessionService.instance.userData?.id ??
// SessionService.instance.userData?.accountId ??
// '';
// final res = await BasicInfoApi.getUserMessage(accountId);
// if (res['success'] == true) {
// final data = res['data'] as Map<String, dynamic>;
// SessionService.instance.updateFromApiResponse(data);
// await SessionService.instance.saveToPrefs();
// setState(() {
// name = SessionService.instance.userData?.name ?? "登录/注册";
// phone = SessionService.instance.userData?.phone ?? "";
// });
// }
}
Future<void> _logout() async {
LoadingDialogHelper.show();
/// 获取用户在职列表
final firmData = await BasicInfoApi.getJoinFirmList();
if (firmData['success'] == true) {
final firmList = firmData['data'];
LoadingDialogHelper.dismiss();
if (firmList.isNotEmpty) {
CustomAlertDialog.showAlert(
context,
title: '温馨提示',
content: '您目前还有入职信息无法直接注销。\n请先在“就职单位”页面中离职。',
);
} else {
final result = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: '注销后您的所有信息将会被删除\n请确认是否注销。 ',
onConfirm: () {},
);
if (result) {
CustomAlertDialog.showInputWithCode(
context,
title: '手机号:${SessionService.instance.phone}',
onGetCode: () async {
LoadingDialogHelper.show();
final res = await BasicInfoApi.sendRegisterSms({'phone': phone});
LoadingDialogHelper.dismiss();
return true;
},
onConfirm: (code) async {
_quit(code);
},
);
}
}
} else {
LoadingDialogHelper.dismiss();
ToastUtil.showNormal(context, firmData['errMessage'] ?? '');
}
}
// 离职
Future<void> _quit(String code) async {
LoadingDialogHelper.show();
Map data = {'id': SessionService.instance.accountId, 'phoneCode': code};
await BasicInfoApi.logout(data).then((res) async {
LoadingDialogHelper.dismiss();
if (res['success'] == true) {
ToastUtil.showNormal(context, '账号已注销');
await SessionService.instance.clear(clearPrefs: true);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const LoginPage()),
);
} else {
ToastUtil.showNormal(context, res['errMessage'] ?? '');
}
});
}
Widget _buildSloganSection() {
final headerUrl = SessionService.instance.userData?.userAvatarUrl ?? '';
return Container(
margin: const EdgeInsets.fromLTRB(0, 100, 0, 0),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const CircleAvatar(
backgroundImage: AssetImage("assets/images/yingyong11.png"),
radius: 30,
),
const SizedBox(width: 16),
Text(
name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
const SizedBox(width: 16),
],
),
);
}
Widget _buildSettingItem({
required String title,
required String icon,
required bool value,
required ValueChanged<bool?> onChanged,
}) {
return GestureDetector(
onTap: () async {
onChanged(value);
},
child: ListTile(
leading: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
child: Image.asset(icon, fit: BoxFit.cover),
),
title: Text(
title,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
trailing: Transform.scale(
scale: 1.2,
child: const Icon(Icons.chevron_right),
),
),
);
}
Future<void> _clearUserSession() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('isLoggedIn'); // 清除登录状态
}
void _onSettingTapAction(String action) async {
switch (action) {
case 'userinfo':
await pushPage(
FullUserinfoPage(isEidt: false, isChooseFirm: true),
context,
);
break;
case 'changePwd':
await pushPage(MineSetPwdPage('0'), context);
break;
case 'scanOnboarding':
final result = await pushPage(
ScanPage(type: ScanType.Onboarding),
context,
);
if (result == null) return;
pushPage(OnboardingFullPage(scanData: result), context);
break;
case 'face':
pushPage(
const FaceRecognitionPage(
studentId: '',
data: {},
mode: FaceMode.setUpdata,
),
context,
);
break;
case 'certificate':
pushPage(const CertificateListPage(), context);
break;
case 'version':
// 版本检查逻辑(占位)
ToastUtil.showNormal(context, '功能开发中...');
break;
case 'about':
// 关于我们(占位)
ToastUtil.showNormal(context, '功能开发中...');
break;
case 'changeAccount':
pushPage(MineChangeFirmPage(), context);
break;
case 'logout':
_logout();
break;
default:
break;
}
}
Widget _buildSettingsList() {
final children = <Widget>[];
for (int i = 0; i < _allSettings.length; i++) {
// 保证索引范围并且按照 _visible 判定
if (i >= _visible.length) continue;
if (!_visible[i]) continue;
final item = _allSettings[i];
final title = item['title'] as String;
// 只在 isChooseFirm 为 true 时显示
if (title == '切换账户' && !widget.isChooseFirm) continue;
children.add(
_buildSettingItem(
title: title,
icon: item['icon'] as String,
value: false,
onChanged: (_) => _onSettingTapAction(item['action'] as String),
),
);
}
return Container(
margin: const EdgeInsets.fromLTRB(20, 0, 20, 0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 2,
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Column(children: children),
);
}
@override
Widget build(BuildContext context) {
RouteModel? showLogot = RouteService().findRouteByPerm('my-center-Logout');
bool showLogout = showLogot?.showFlag == 1;
final double headerHeight = 300.0;
final double overlap = 100.0;
return SizedBox(
height: MediaQuery.of(context).size.height,
child: Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
height: headerHeight,
child: _buildHeaderSection(),
),
Positioned.fill(
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: (overscroll) {
overscroll.disallowIndicator();
return false;
},
child: ListView(
padding: EdgeInsets.only(
top: headerHeight - overlap,
bottom: 24,
left: 0,
right: 0,
),
children: [
_buildSettingsList(),
const SizedBox(height: 15),
if (showLogout)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: CustomButton(
text: '退出登录',
textStyle: const TextStyle(fontSize: 16),
backgroundColor: Colors.blue,
onPressed: () {
CustomAlertDialog.showConfirm(
context,
title: '确认退出',
content: '确定要退出当前账号吗',
onConfirm: () async {
await _clearUserSession();
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const LoginPage(),
),
(Route<dynamic> route) => false,
);
},
);
},
),
),
],
),
),
),
],
),
);
}
Widget _buildHeaderSection() {
return Stack(
alignment: const FractionalOffset(0.5, 0),
children: [
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Image.asset(
"assets/images/my_bg.png",
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
),
),
const Positioned(
top: 51,
child: Text(
"我的",
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
_buildSloganSection(),
],
);
}
}