profile_page.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:go_router/go_router.dart';
  4. import 'package:tdesign_flutter/tdesign_flutter.dart';
  5. import '../../core/i18n/app_localizations.dart';
  6. import '../../core/i18n/locale_provider.dart';
  7. import '../../core/theme/theme_mode_provider.dart';
  8. import '../../core/auth/role_provider.dart';
  9. import '../../shared/widgets/profile_menu_item.dart';
  10. import '../../core/theme/app_colors.dart';
  11. import '../../core/theme/app_colors_extension.dart';
  12. class ProfilePage extends ConsumerWidget {
  13. const ProfilePage({super.key});
  14. @override
  15. Widget build(BuildContext context, WidgetRef ref) {
  16. final l10n = AppLocalizations.of(context);
  17. final currentLocale = ref.watch(localeProvider);
  18. final themeMode = ref.watch(themeModeProvider);
  19. final isDark = themeMode == ThemeMode.dark;
  20. final currentRole = ref.watch(currentRoleProvider);
  21. final isAdmin = currentRole == 'admin';
  22. return SingleChildScrollView(
  23. physics: const AlwaysScrollableScrollPhysics(),
  24. child: Column(
  25. children: [
  26. const SizedBox(height: 16),
  27. _buildUserInfoCard(context, l10n),
  28. const SizedBox(height: 16),
  29. // 功能列表
  30. Padding(
  31. padding: const EdgeInsets.symmetric(horizontal: 16),
  32. child: Column(
  33. children: [
  34. // ── 角色切换(测试用)──
  35. ProfileMenuItem(
  36. icon: Icons.people,
  37. label: '角色切换',
  38. trailing: _roleName(currentRole),
  39. onTap: () => _showRoleSheet(context, ref, currentRole),
  40. ),
  41. const SizedBox(height: 8),
  42. // ── 语言设置 ──
  43. ProfileMenuItem(
  44. icon: Icons.language,
  45. label: l10n.get('language'),
  46. trailing: _localeName(currentLocale),
  47. onTap: () => _showLanguageSheet(context, ref, currentLocale),
  48. ),
  49. const SizedBox(height: 8),
  50. // ── 深色模式 ──
  51. ProfileMenuItem(
  52. icon: Icons.dark_mode,
  53. label: l10n.get('darkMode'),
  54. trailingWidget: TDSwitch(
  55. isOn: isDark,
  56. onChanged: (v) {
  57. ref.read(themeModeProvider.notifier).toggle();
  58. return v;
  59. },
  60. ),
  61. ),
  62. const SizedBox(height: 8),
  63. if (isAdmin) ...[
  64. ProfileMenuItem(
  65. icon: Icons.admin_panel_settings,
  66. label: '权限管理',
  67. onTap: () => context.push('/admin/permissions'),
  68. ),
  69. const SizedBox(height: 8),
  70. ],
  71. // ── 关于 ──
  72. ProfileMenuItem(
  73. icon: Icons.info_outline,
  74. label: l10n.get('about'),
  75. onTap: () => _showAboutDialog(context, l10n),
  76. ),
  77. ],
  78. ),
  79. ),
  80. const SizedBox(height: 8),
  81. TDFooter(TDFooterType.text, text: l10n.get('version')),
  82. ],
  83. ),
  84. );
  85. }
  86. Widget _buildUserInfoCard(BuildContext context, AppLocalizations l10n) {
  87. final colors = Theme.of(context).extension<AppColorsExtension>()!;
  88. return Container(
  89. margin: const EdgeInsets.symmetric(horizontal: 16),
  90. padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
  91. decoration: BoxDecoration(
  92. color: colors.bgCard,
  93. borderRadius: BorderRadius.circular(12),
  94. ),
  95. child: Row(
  96. children: [
  97. // 头像 - 点击更换占位
  98. GestureDetector(
  99. onTap: () {
  100. TDToast.showText(
  101. '头像更换功能(占位)',
  102. context: context,
  103. duration: Duration(seconds: 2),
  104. );
  105. },
  106. child: Container(
  107. width: 64,
  108. height: 64,
  109. decoration: BoxDecoration(
  110. color: colors.primaryLight,
  111. shape: BoxShape.circle,
  112. ),
  113. child: Center(
  114. child: Text(
  115. '张',
  116. style: TextStyle(
  117. fontSize: 28,
  118. fontWeight: FontWeight.w700,
  119. color: colors.primary,
  120. ),
  121. ),
  122. ),
  123. ),
  124. ),
  125. const SizedBox(width: 16),
  126. // 用户信息
  127. Expanded(
  128. child: Column(
  129. crossAxisAlignment: CrossAxisAlignment.start,
  130. children: [
  131. Text(
  132. l10n.get('userName'),
  133. style: TextStyle(
  134. fontSize: AppFontSizes.title,
  135. fontWeight: FontWeight.w600,
  136. color: colors.textPrimary,
  137. ),
  138. ),
  139. const SizedBox(height: AppSpacing.xs),
  140. Text(
  141. l10n.get('salesDepartment'),
  142. style: TextStyle(
  143. fontSize: AppFontSizes.body,
  144. color: colors.textSecondary,
  145. ),
  146. ),
  147. const SizedBox(height: 2),
  148. Text(
  149. '销售经理',
  150. style: TextStyle(fontSize: 12, color: colors.textPlaceholder),
  151. ),
  152. ],
  153. ),
  154. ),
  155. // 箭头
  156. Icon(Icons.chevron_right, size: 16, color: colors.textPlaceholder),
  157. ],
  158. ),
  159. );
  160. }
  161. String _localeName(Locale locale) {
  162. if (locale.languageCode == 'zh' && locale.countryCode == 'TW') {
  163. return '繁體中文';
  164. }
  165. if (locale.languageCode == 'en') {
  166. return 'English';
  167. }
  168. return '简体中文';
  169. }
  170. String _roleName(String role) {
  171. for (final (value, label) in roleOptions) {
  172. if (value == role) return label;
  173. }
  174. return role;
  175. }
  176. void _showRoleSheet(
  177. BuildContext context,
  178. WidgetRef ref,
  179. String currentRole,
  180. ) {
  181. final colors = Theme.of(context).extension<AppColorsExtension>()!;
  182. TDActionSheet.showListActionSheet(
  183. context,
  184. showCancel: false,
  185. useSafeArea: false,
  186. items: roleOptions.map((r) {
  187. final isSelected = r.$1 == currentRole;
  188. return TDActionSheetItem(
  189. label: isSelected ? '${r.$2} ✓' : r.$2,
  190. textStyle: TextStyle(
  191. color: isSelected ? colors.primary : colors.textPrimary,
  192. fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
  193. ),
  194. );
  195. }).toList(),
  196. onSelected: (item, index) {
  197. ref.read(currentRoleProvider.notifier).state = roleOptions[index].$1;
  198. },
  199. );
  200. }
  201. void _showLanguageSheet(
  202. BuildContext context,
  203. WidgetRef ref,
  204. Locale currentLocale,
  205. ) {
  206. final colors = Theme.of(context).extension<AppColorsExtension>()!;
  207. final languages = [
  208. (label: '简体中文', locale: const Locale('zh', 'CN')),
  209. (label: 'English', locale: const Locale('en')),
  210. (label: '繁體中文', locale: const Locale('zh', 'TW')),
  211. ];
  212. TDActionSheet.showListActionSheet(
  213. context,
  214. showCancel: false,
  215. useSafeArea: false,
  216. items: languages.map((lang) {
  217. final isSelected =
  218. lang.locale.languageCode == currentLocale.languageCode &&
  219. lang.locale.countryCode == currentLocale.countryCode;
  220. return TDActionSheetItem(
  221. label: isSelected ? '${lang.label} ✓' : lang.label,
  222. textStyle: TextStyle(
  223. color: isSelected ? colors.primary : colors.textPrimary,
  224. fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
  225. ),
  226. );
  227. }).toList(),
  228. onSelected: (item, index) {
  229. ref.read(localeProvider.notifier).setLocale(languages[index].locale);
  230. },
  231. );
  232. }
  233. void _showAboutDialog(BuildContext context, AppLocalizations l10n) {
  234. final colors = Theme.of(context).extension<AppColorsExtension>()!;
  235. showGeneralDialog(
  236. context: context,
  237. pageBuilder:
  238. (
  239. BuildContext buildContext,
  240. Animation<double> animation,
  241. Animation<double> secondaryAnimation,
  242. ) {
  243. return TDConfirmDialog(
  244. title: l10n.get('about'),
  245. titleColor: colors.textPrimary,
  246. contentWidget: Column(
  247. mainAxisSize: MainAxisSize.min,
  248. crossAxisAlignment: CrossAxisAlignment.start,
  249. children: [
  250. _aboutRow(
  251. l10n.get('version'),
  252. 'v1.0.0 (Build 20260601)',
  253. colors,
  254. ),
  255. const Divider(height: 24),
  256. GestureDetector(
  257. onTap: () {
  258. Navigator.of(buildContext).pop();
  259. TDToast.showText(
  260. '用户协议(占位)',
  261. context: context,
  262. duration: Duration(seconds: 2),
  263. );
  264. },
  265. child: Text(
  266. '用户协议',
  267. style: TextStyle(fontSize: 14, color: colors.primary),
  268. ),
  269. ),
  270. const SizedBox(height: 12),
  271. GestureDetector(
  272. onTap: () {
  273. Navigator.of(buildContext).pop();
  274. TDToast.showText(
  275. '隐私政策(占位)',
  276. context: context,
  277. duration: Duration(seconds: 2),
  278. );
  279. },
  280. child: Text(
  281. '隐私政策',
  282. style: TextStyle(fontSize: 14, color: colors.primary),
  283. ),
  284. ),
  285. ],
  286. ),
  287. buttonStyle: TDDialogButtonStyle.text,
  288. buttonText: l10n.get('close'),
  289. buttonTextColor: colors.primary,
  290. action: () => Navigator.of(buildContext).pop(),
  291. );
  292. },
  293. );
  294. }
  295. Widget _aboutRow(String label, String value, AppColorsExtension colors) {
  296. return Row(
  297. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  298. children: [
  299. Text(
  300. label,
  301. style: TextStyle(fontSize: 14, color: colors.textSecondary),
  302. ),
  303. Flexible(
  304. child: Text(
  305. value,
  306. textAlign: TextAlign.right,
  307. softWrap: true,
  308. style: TextStyle(fontSize: 14, color: colors.textPrimary),
  309. ),
  310. ),
  311. ],
  312. );
  313. }
  314. }