// screens/me/me_screen.dart — 「我的」hub(镜像原型 m-me.html,移动愿景导航中枢)。 // 结构:用户头卡(54 渐变圆头像 + 真名 + 店名·账号 + 角色徽章) // + 经营管理组(往来单位/基础数据/库存盘点) // + 系统组(用户管理/授权管理/设备管理/系统设置/主题外观/关于我们) // + 退出登录(确认 sheet)。窄屏底部 tab「我的」入口;桌面侧栏不含此屏。 import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../core/auth/auth_state.dart'; import '../../core/config/store_compliance.dart'; import '../../core/theme/app_dims.g.dart'; import '../../core/theme/context_tokens.dart'; import '../../providers/shop_provider.dart'; import '../../widgets/ds/ds_atoms.dart'; import '../../widgets/ds/m_hub.dart'; import '../../widgets/ds/m_sheet.dart'; import '../../widgets/ds/status_icon_map.dart'; import '../../widgets/theme_sheet.dart'; String _roleLabel(String role) { switch (role) { case 'superadmin': return '超级管理员'; case 'admin': return '管理员'; case 'readonly': return '只读'; default: return '操作员'; } } class MeScreen extends ConsumerWidget { const MeScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final t = context.tokens; final user = ref.watch(authStateProvider).user; final shopName = ref.watch(shopInfoProvider).valueOrNull?.name ?? user?.shopNo ?? ''; return Container( color: t.bg, child: ListView( padding: const EdgeInsets.all(14), children: [ if (user != null) _header(t, user, shopName), MHubGroup(label: '经营管理', items: [ MHubItem( icon: LucideIcons.users, label: '往来单位', onTap: () => context.go('/partners')), MHubItem( icon: LucideIcons.layoutGrid, label: '基础数据', onTap: () => context.go('/products')), MHubItem( icon: LucideIcons.clipboardCheck, label: '库存盘点', onTap: () => context.go('/inventory/check')), ]), MHubGroup(label: '系统', items: [ MHubItem( icon: LucideIcons.userPlus, label: '用户管理', onTap: () => context.go('/settings/users')), MHubItem( icon: LucideIcons.trendingUp, label: '授权管理', onTap: () => context.go('/me/license')), MHubItem( icon: LucideIcons.monitorSmartphone, label: '设备管理', onTap: () => context.go('/devices')), MHubItem( icon: LucideIcons.settings, label: '系统设置', onTap: () => context.go('/settings')), MHubItem( icon: LucideIcons.shirt, label: '主题外观', onTap: () => showThemeSheet(context)), MHubItem( icon: LucideIcons.info, label: '关于我们', onTap: () => context.go('/about')), // iOS App Store 审核 5.1.1(v):应用内须可发起账号删除 if (isIosAppStoreBuild) MHubItem( icon: LucideIcons.userX, label: '注销账号', onTap: () => _showDeleteAccountSheet(context, user)), ]), const SizedBox(height: 18), _LogoutButton(onConfirm: () { ref.read(authStateProvider.notifier).logout(); context.go('/login'); }), ], ), ); } /// 用户头卡(原型 .me-hd):surface 卡 + 54 渐变圆头像 + 名/店·账号 + 角色徽章。 Widget _header(dynamic t, AuthUser user, String shopName) { final name = user.realName.isNotEmpty ? user.realName : user.username; final initial = name.isNotEmpty ? name.characters.first : '用'; final roleLabel = _roleLabel(user.role); return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: t.surface, border: Border.all(color: t.border), borderRadius: BorderRadius.circular(AppDims.rLg), ), child: Row(children: [ Container( width: 54, height: 54, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [t.primary, t.primaryDark], ), shape: BoxShape.circle, ), alignment: Alignment.center, child: Text(initial, style: TextStyle( color: t.onPrimary, fontSize: 22, fontWeight: FontWeight.w800)), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(name, style: TextStyle( fontSize: AppDims.fsH2, fontWeight: FontWeight.w700, color: t.heading)), const SizedBox(height: 3), Text('$shopName · ${user.username}', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: AppDims.fsSm, color: t.muted)), ], ), ), DsBadge(roleLabel, tone: DsBadgeTone.info, icon: statusIcon(roleLabel)), ]), ); } /// 注销账号(iOS App Store 审核 5.1.1(v)):应用内发起 → 邮件人工核验。 /// 措辞保持中性,不涉及任何购买/续费引导。 void _showDeleteAccountSheet(BuildContext context, AuthUser? user) { showMSheet( context, title: '注销账号', builder: (ctx) { final t = ctx.tokens; Widget note(String s) => Padding( padding: const EdgeInsets.only(top: 4), child: Text('· $s', style: TextStyle( fontSize: AppDims.fsSm, color: t.muted, height: 1.6)), ); final who = user != null ? '${user.shopNo} · ${user.username}' : ''; return Padding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('注销将删除您的登录账号,操作不可恢复。', style: TextStyle( fontSize: AppDims.fsBody, fontWeight: FontWeight.w600, color: t.heading)), const SizedBox(height: 8), note('账号与登录凭据将被删除,无法找回'), note('门店经营数据归属门店:门店内仍有其他用户时数据保留,' '需整店删除请在申请中说明'), note('为防止误操作与冒用,注销需人工核验身份,' '我们将在收到申请后 15 个工作日内完成处理'), const SizedBox(height: 16), SizedBox( width: double.infinity, child: DsButton('发送注销申请邮件', variant: DsBtnVariant.primary, onPressed: () { final subject = Uri.encodeComponent('账号注销申请($who)'); final body = Uri.encodeComponent( '申请注销账号:$who\n注销原因(选填):\n\n' '请使用注册时预留的联系方式发送本邮件以便核验。'); launchUrl(Uri.parse( 'mailto:$supportEmail?subject=$subject&body=$body')); }), ), const SizedBox(height: 8), Center( child: Text('客服邮箱 $supportEmail', style: TextStyle(fontSize: AppDims.fsXs, color: t.faint)), ), ], ), ); }, ); } } class _LogoutButton extends StatelessWidget { final VoidCallback onConfirm; const _LogoutButton({required this.onConfirm}); @override Widget build(BuildContext context) { final t = context.tokens; // Material 自带(InkWell 水波 + golden 直挂无 Scaffold 场景) return Material( color: t.surface, borderRadius: BorderRadius.circular(AppDims.rMd), child: InkWell( onTap: () => _confirm(context), borderRadius: BorderRadius.circular(AppDims.rMd), child: Container( height: 44, alignment: Alignment.center, decoration: BoxDecoration( border: Border.all(color: t.border), borderRadius: BorderRadius.circular(AppDims.rMd), ), child: Text('退出登录', style: TextStyle( fontSize: AppDims.fsBody, fontWeight: FontWeight.w600, color: t.danger)), ), ), ); } void _confirm(BuildContext context) { showMSheet( context, title: '退出登录', builder: (ctx) => Text('确认退出当前账号?', style: TextStyle( fontSize: AppDims.fsBody, color: ctx.tokens.text)), actions: [ Builder( builder: (ctx) => DsButton('取消', onPressed: () => Navigator.of(ctx).pop())), Builder( builder: (ctx) => DsButton('退出登录', variant: DsBtnVariant.danger, onPressed: () { Navigator.of(ctx).pop(); onConfirm(); }), ), ], ); } }