498 lines
15 KiB
Dart
498 lines
15 KiB
Dart
// 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': ['dashboard-scan'],
|
||
'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 {
|
||
final res = await BasicInfoApi.getUserMessage(
|
||
'${SessionService.instance.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) {
|
||
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),
|
||
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(),
|
||
],
|
||
);
|
||
}
|
||
} |