// 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 createState() => MinePageState(); } class MinePageState extends State { // 设置项状态 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> _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 _visible; void onRouteConfigLoaded() { if (mounted) { setState(() { // 这里不直接修改业务逻辑,仅触发重建(update 会在 listener 中执行) }); } } @override void initState() { super.initState(); _getUserInfo(); // 初始默认都显示,避免短时白屏/闪烁 _visible = List.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 next = List.filled(_allSettings.length, false); for (int i = 0; i < _allSettings.length; i++) { final perms = (_allSettings[i]['perms'] ?? []) as List; 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 _getUserInfo() async { final res = await BasicInfoApi.getUserMessage( '${SessionService.instance.accountId}', ); if (res['success'] == true) { final data = res['data'] as Map; SessionService.instance.updateFromApiResponse(data); await SessionService.instance.saveToPrefs(); setState(() { name = SessionService.instance.userData?.name ?? "登录/注册"; phone = SessionService.instance.userData?.phone ?? ""; }); } } Future _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 _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 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 _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 = []; 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( 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 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(), ], ); } }