feat(mobile-app): implement avatar upload with image picker
- Add image picker for camera and gallery selection - Add uploadAvatar method in AccountService - Support SVG and image URL display in ProfilePage and EditProfilePage - Add avatarUrl storage key for uploaded avatars - Show upload progress indicator during avatar upload 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
97ef204f7c
commit
39db791a30
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import '../network/api_client.dart';
|
import '../network/api_client.dart';
|
||||||
|
|
@ -553,7 +554,7 @@ class AccountService {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取头像 SVG
|
/// 获取头像 SVG(初始生成的头像)
|
||||||
Future<String?> getAvatarSvg() async {
|
Future<String?> getAvatarSvg() async {
|
||||||
debugPrint('$_tag getAvatarSvg() - 获取头像 SVG');
|
debugPrint('$_tag getAvatarSvg() - 获取头像 SVG');
|
||||||
final result = await _secureStorage.read(key: StorageKeys.avatarSvg);
|
final result = await _secureStorage.read(key: StorageKeys.avatarSvg);
|
||||||
|
|
@ -561,6 +562,14 @@ class AccountService {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取头像 URL(用户上传的头像)
|
||||||
|
Future<String?> getAvatarUrl() async {
|
||||||
|
debugPrint('$_tag getAvatarUrl() - 获取头像 URL');
|
||||||
|
final result = await _secureStorage.read(key: StorageKeys.avatarUrl);
|
||||||
|
debugPrint('$_tag getAvatarUrl() - 结果: $result');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// 获取推荐码
|
/// 获取推荐码
|
||||||
Future<String?> getReferralCode() async {
|
Future<String?> getReferralCode() async {
|
||||||
debugPrint('$_tag getReferralCode() - 获取推荐码');
|
debugPrint('$_tag getReferralCode() - 获取推荐码');
|
||||||
|
|
@ -819,6 +828,79 @@ class AccountService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 上传头像
|
||||||
|
///
|
||||||
|
/// [imageFile] - 图片文件
|
||||||
|
/// 返回新的头像URL
|
||||||
|
Future<String> uploadAvatar(File imageFile) async {
|
||||||
|
debugPrint('$_tag uploadAvatar() - 开始上传头像');
|
||||||
|
debugPrint('$_tag uploadAvatar() - 文件路径: ${imageFile.path}');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取文件名和MIME类型
|
||||||
|
final fileName = imageFile.path.split('/').last;
|
||||||
|
final extension = fileName.split('.').last.toLowerCase();
|
||||||
|
String mimeType;
|
||||||
|
switch (extension) {
|
||||||
|
case 'jpg':
|
||||||
|
case 'jpeg':
|
||||||
|
mimeType = 'image/jpeg';
|
||||||
|
break;
|
||||||
|
case 'png':
|
||||||
|
mimeType = 'image/png';
|
||||||
|
break;
|
||||||
|
case 'gif':
|
||||||
|
mimeType = 'image/gif';
|
||||||
|
break;
|
||||||
|
case 'webp':
|
||||||
|
mimeType = 'image/webp';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mimeType = 'image/jpeg';
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('$_tag uploadAvatar() - 文件名: $fileName, MIME: $mimeType');
|
||||||
|
|
||||||
|
// 创建 FormData
|
||||||
|
final formData = FormData.fromMap({
|
||||||
|
'file': await MultipartFile.fromFile(
|
||||||
|
imageFile.path,
|
||||||
|
filename: fileName,
|
||||||
|
contentType: DioMediaType.parse(mimeType),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调用 API
|
||||||
|
debugPrint('$_tag uploadAvatar() - 调用 POST /user/upload-avatar');
|
||||||
|
final response = await _apiClient.post(
|
||||||
|
'/user/upload-avatar',
|
||||||
|
data: formData,
|
||||||
|
);
|
||||||
|
debugPrint('$_tag uploadAvatar() - API 响应状态码: ${response.statusCode}');
|
||||||
|
|
||||||
|
if (response.data == null) {
|
||||||
|
throw const ApiException('上传头像失败: 空响应');
|
||||||
|
}
|
||||||
|
|
||||||
|
final responseData = response.data as Map<String, dynamic>;
|
||||||
|
final newAvatarUrl = responseData['avatarUrl'] as String;
|
||||||
|
debugPrint('$_tag uploadAvatar() - 新头像URL: $newAvatarUrl');
|
||||||
|
|
||||||
|
// 更新本地存储(使用 avatarUrl 而不是 avatarSvg)
|
||||||
|
await _secureStorage.write(key: StorageKeys.avatarUrl, value: newAvatarUrl);
|
||||||
|
debugPrint('$_tag uploadAvatar() - 本地存储已更新');
|
||||||
|
|
||||||
|
return newAvatarUrl;
|
||||||
|
} on ApiException catch (e) {
|
||||||
|
debugPrint('$_tag uploadAvatar() - API 异常: $e');
|
||||||
|
rethrow;
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
debugPrint('$_tag uploadAvatar() - 未知异常: $e');
|
||||||
|
debugPrint('$_tag uploadAvatar() - 堆栈: $stackTrace');
|
||||||
|
throw ApiException('上传头像失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 获取我的资料(从服务器)
|
/// 获取我的资料(从服务器)
|
||||||
Future<Map<String, dynamic>> getMyProfile() async {
|
Future<Map<String, dynamic>> getMyProfile() async {
|
||||||
debugPrint('$_tag getMyProfile() - 获取我的资料');
|
debugPrint('$_tag getMyProfile() - 获取我的资料');
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ class StorageKeys {
|
||||||
// 账号信息
|
// 账号信息
|
||||||
static const String userSerialNum = 'user_serial_num'; // 用户序列号
|
static const String userSerialNum = 'user_serial_num'; // 用户序列号
|
||||||
static const String username = 'username'; // 随机用户名
|
static const String username = 'username'; // 随机用户名
|
||||||
static const String avatarSvg = 'avatar_svg'; // 随机 SVG 头像
|
static const String avatarSvg = 'avatar_svg'; // 随机 SVG 头像(初始生成)
|
||||||
|
static const String avatarUrl = 'avatar_url'; // 用户上传的头像URL
|
||||||
static const String referralCode = 'referral_code'; // 推荐码
|
static const String referralCode = 'referral_code'; // 推荐码
|
||||||
static const String isAccountCreated = 'is_account_created'; // 账号是否已创建
|
static const String isAccountCreated = 'is_account_created'; // 账号是否已创建
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import '../../../../core/di/injection_container.dart';
|
import '../../../../core/di/injection_container.dart';
|
||||||
|
|
||||||
/// 编辑资料页面 - 允许用户修改头像和昵称
|
/// 编辑资料页面 - 允许用户修改头像和昵称
|
||||||
|
|
@ -17,9 +19,18 @@ class _EditProfilePageState extends ConsumerState<EditProfilePage> {
|
||||||
// 昵称控制器
|
// 昵称控制器
|
||||||
final TextEditingController _nicknameController = TextEditingController();
|
final TextEditingController _nicknameController = TextEditingController();
|
||||||
|
|
||||||
// 当前头像SVG
|
// 图片选择器
|
||||||
|
final ImagePicker _imagePicker = ImagePicker();
|
||||||
|
|
||||||
|
// 当前头像SVG(初始生成的头像)
|
||||||
String? _avatarSvg;
|
String? _avatarSvg;
|
||||||
|
|
||||||
|
// 当前头像URL(用户上传的头像)
|
||||||
|
String? _avatarUrl;
|
||||||
|
|
||||||
|
// 本地选择的图片文件(待上传)
|
||||||
|
File? _selectedImageFile;
|
||||||
|
|
||||||
// 原始昵称(用于判断是否修改)
|
// 原始昵称(用于判断是否修改)
|
||||||
String? _originalNickname;
|
String? _originalNickname;
|
||||||
|
|
||||||
|
|
@ -29,6 +40,9 @@ class _EditProfilePageState extends ConsumerState<EditProfilePage> {
|
||||||
// 是否正在加载
|
// 是否正在加载
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
// 是否正在上传头像
|
||||||
|
bool _isUploadingAvatar = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -41,12 +55,14 @@ class _EditProfilePageState extends ConsumerState<EditProfilePage> {
|
||||||
|
|
||||||
final username = await accountService.getUsername();
|
final username = await accountService.getUsername();
|
||||||
final avatarSvg = await accountService.getAvatarSvg();
|
final avatarSvg = await accountService.getAvatarSvg();
|
||||||
|
final avatarUrl = await accountService.getAvatarUrl();
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_nicknameController.text = username ?? '';
|
_nicknameController.text = username ?? '';
|
||||||
_originalNickname = username;
|
_originalNickname = username;
|
||||||
_avatarSvg = avatarSvg;
|
_avatarSvg = avatarSvg;
|
||||||
|
_avatarUrl = avatarUrl;
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -73,38 +89,120 @@ class _EditProfilePageState extends ConsumerState<EditProfilePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 拍照
|
/// 拍照
|
||||||
void _takePhoto() {
|
Future<void> _takePhoto() async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
// TODO: 实现拍照功能
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
try {
|
||||||
const SnackBar(
|
final XFile? image = await _imagePicker.pickImage(
|
||||||
content: Text('拍照功能开发中'),
|
source: ImageSource.camera,
|
||||||
backgroundColor: Color(0xFFD4AF37),
|
maxWidth: 512,
|
||||||
),
|
maxHeight: 512,
|
||||||
);
|
imageQuality: 85,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (image != null && mounted) {
|
||||||
|
setState(() {
|
||||||
|
_selectedImageFile = File(image.path);
|
||||||
|
});
|
||||||
|
// 立即上传头像
|
||||||
|
await _uploadSelectedAvatar();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('拍照失败: ${e.toString()}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从相册选择
|
/// 从相册选择
|
||||||
void _pickFromGallery() {
|
Future<void> _pickFromGallery() async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
// TODO: 实现相册选择功能
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
try {
|
||||||
const SnackBar(
|
final XFile? image = await _imagePicker.pickImage(
|
||||||
content: Text('相册选择功能开发中'),
|
source: ImageSource.gallery,
|
||||||
backgroundColor: Color(0xFFD4AF37),
|
maxWidth: 512,
|
||||||
),
|
maxHeight: 512,
|
||||||
);
|
imageQuality: 85,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (image != null && mounted) {
|
||||||
|
setState(() {
|
||||||
|
_selectedImageFile = File(image.path);
|
||||||
|
});
|
||||||
|
// 立即上传头像
|
||||||
|
await _uploadSelectedAvatar();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('选择图片失败: ${e.toString()}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 删除头像
|
/// 上传选中的头像
|
||||||
|
Future<void> _uploadSelectedAvatar() async {
|
||||||
|
if (_selectedImageFile == null) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isUploadingAvatar = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final accountService = ref.read(accountServiceProvider);
|
||||||
|
final newAvatarUrl = await accountService.uploadAvatar(_selectedImageFile!);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_avatarUrl = newAvatarUrl;
|
||||||
|
_selectedImageFile = null; // 清除本地文件引用
|
||||||
|
_isUploadingAvatar = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('头像上传成功'),
|
||||||
|
backgroundColor: Color(0xFFD4AF37),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_selectedImageFile = null;
|
||||||
|
_isUploadingAvatar = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('头像上传失败: ${e.toString().replaceAll('Exception: ', '')}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除头像(恢复默认SVG头像)
|
||||||
void _deleteAvatar() {
|
void _deleteAvatar() {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
setState(() {
|
setState(() {
|
||||||
_avatarPath = null;
|
_avatarUrl = null;
|
||||||
|
_selectedImageFile = null;
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('头像已删除'),
|
content: Text('已恢复默认头像'),
|
||||||
backgroundColor: Color(0xFFD4AF37),
|
backgroundColor: Color(0xFFD4AF37),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -278,41 +376,50 @@ class _EditProfilePageState extends ConsumerState<EditProfilePage> {
|
||||||
/// 构建头像区域
|
/// 构建头像区域
|
||||||
Widget _buildAvatarSection() {
|
Widget _buildAvatarSection() {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: _showAvatarPicker,
|
onTap: _isUploadingAvatar ? null : _showAvatarPicker,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// 头像
|
// 头像
|
||||||
Container(
|
Stack(
|
||||||
width: 128,
|
children: [
|
||||||
height: 128,
|
Container(
|
||||||
decoration: BoxDecoration(
|
width: 128,
|
||||||
shape: BoxShape.circle,
|
height: 128,
|
||||||
color: const Color(0xFFFFF5E6),
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
shape: BoxShape.circle,
|
||||||
color: const Color(0x33D4AF37),
|
color: const Color(0xFFFFF5E6),
|
||||||
width: 2,
|
border: Border.all(
|
||||||
|
color: const Color(0x33D4AF37),
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ClipOval(
|
||||||
|
child: _buildAvatarContent(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
// 上传中的loading指示器
|
||||||
child: ClipOval(
|
if (_isUploadingAvatar)
|
||||||
child: _avatarSvg != null
|
Positioned.fill(
|
||||||
? SvgPicture.string(
|
child: Container(
|
||||||
_avatarSvg!,
|
decoration: BoxDecoration(
|
||||||
width: 128,
|
shape: BoxShape.circle,
|
||||||
height: 128,
|
color: Colors.black.withOpacity(0.5),
|
||||||
fit: BoxFit.cover,
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
Icons.person,
|
|
||||||
size: 64,
|
|
||||||
color: Color(0xFF8B5A2B),
|
|
||||||
),
|
),
|
||||||
),
|
child: const Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
strokeWidth: 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 修改头像文字
|
// 修改头像文字
|
||||||
const Text(
|
Text(
|
||||||
'修改头像',
|
_isUploadingAvatar ? '上传中...' : '修改头像',
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|
@ -325,6 +432,66 @@ class _EditProfilePageState extends ConsumerState<EditProfilePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 构建头像内容(支持本地文件、网络URL、SVG)
|
||||||
|
Widget _buildAvatarContent() {
|
||||||
|
// 优先显示本地选中的图片(正在上传中)
|
||||||
|
if (_selectedImageFile != null) {
|
||||||
|
return Image.file(
|
||||||
|
_selectedImageFile!,
|
||||||
|
width: 128,
|
||||||
|
height: 128,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其次显示已上传的网络图片URL
|
||||||
|
if (_avatarUrl != null && _avatarUrl!.isNotEmpty) {
|
||||||
|
return Image.network(
|
||||||
|
_avatarUrl!,
|
||||||
|
width: 128,
|
||||||
|
height: 128,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: loadingProgress.expectedTotalBytes != null
|
||||||
|
? loadingProgress.cumulativeBytesLoaded /
|
||||||
|
loadingProgress.expectedTotalBytes!
|
||||||
|
: null,
|
||||||
|
valueColor: const AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
// 加载失败时显示SVG或默认头像
|
||||||
|
return _buildSvgOrDefaultAvatar();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后显示SVG或默认头像
|
||||||
|
return _buildSvgOrDefaultAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 构建SVG或默认头像
|
||||||
|
Widget _buildSvgOrDefaultAvatar() {
|
||||||
|
if (_avatarSvg != null) {
|
||||||
|
return SvgPicture.string(
|
||||||
|
_avatarSvg!,
|
||||||
|
width: 128,
|
||||||
|
height: 128,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const Icon(
|
||||||
|
Icons.person,
|
||||||
|
size: 64,
|
||||||
|
color: Color(0xFF8B5A2B),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 构建昵称输入区域
|
/// 构建昵称输入区域
|
||||||
Widget _buildNicknameSection() {
|
Widget _buildNicknameSection() {
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
String _nickname = '加载中...';
|
String _nickname = '加载中...';
|
||||||
String _serialNumber = '--';
|
String _serialNumber = '--';
|
||||||
String? _avatarSvg;
|
String? _avatarSvg;
|
||||||
|
String? _avatarUrl;
|
||||||
final String _referrerSerial = '87654321';
|
final String _referrerSerial = '87654321';
|
||||||
final String _community = '星空社区';
|
final String _community = '星空社区';
|
||||||
final String _parentCommunity = '银河社区';
|
final String _parentCommunity = '银河社区';
|
||||||
|
|
@ -71,12 +72,14 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
final username = await accountService.getUsername();
|
final username = await accountService.getUsername();
|
||||||
final serialNum = await accountService.getUserSerialNum();
|
final serialNum = await accountService.getUserSerialNum();
|
||||||
final avatarSvg = await accountService.getAvatarSvg();
|
final avatarSvg = await accountService.getAvatarSvg();
|
||||||
|
final avatarUrl = await accountService.getAvatarUrl();
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_nickname = username ?? '未设置昵称';
|
_nickname = username ?? '未设置昵称';
|
||||||
_serialNumber = serialNum?.toString() ?? '--';
|
_serialNumber = serialNum?.toString() ?? '--';
|
||||||
_avatarSvg = avatarSvg;
|
_avatarSvg = avatarSvg;
|
||||||
|
_avatarUrl = avatarUrl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -195,8 +198,12 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 编辑资料
|
/// 编辑资料
|
||||||
void _goToEditProfile() {
|
Future<void> _goToEditProfile() async {
|
||||||
context.push(RoutePaths.editProfile);
|
final result = await context.push<bool>(RoutePaths.editProfile);
|
||||||
|
// 如果编辑页面返回 true,说明有更新,刷新用户数据
|
||||||
|
if (result == true && mounted) {
|
||||||
|
_loadUserData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 格式化数字
|
/// 格式化数字
|
||||||
|
|
@ -282,18 +289,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(40),
|
borderRadius: BorderRadius.circular(40),
|
||||||
child: _avatarSvg != null
|
child: _buildAvatarContent(),
|
||||||
? SvgPicture.string(
|
|
||||||
_avatarSvg!,
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
Icons.person,
|
|
||||||
size: 40,
|
|
||||||
color: Color(0xFF8B5A2B),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -344,6 +340,56 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 构建头像内容(支持网络URL和SVG)
|
||||||
|
Widget _buildAvatarContent() {
|
||||||
|
// 优先显示已上传的网络图片URL
|
||||||
|
if (_avatarUrl != null && _avatarUrl!.isNotEmpty) {
|
||||||
|
return Image.network(
|
||||||
|
_avatarUrl!,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return const Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
// 加载失败时显示SVG或默认头像
|
||||||
|
return _buildSvgOrDefaultAvatar();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示SVG或默认头像
|
||||||
|
return _buildSvgOrDefaultAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 构建SVG或默认头像
|
||||||
|
Widget _buildSvgOrDefaultAvatar() {
|
||||||
|
if (_avatarSvg != null) {
|
||||||
|
return SvgPicture.string(
|
||||||
|
_avatarSvg!,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const Icon(
|
||||||
|
Icons.person,
|
||||||
|
size: 40,
|
||||||
|
color: Color(0xFF8B5A2B),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 构建推荐人信息卡片
|
/// 构建推荐人信息卡片
|
||||||
Widget _buildReferralInfoCard() {
|
Widget _buildReferralInfoCard() {
|
||||||
return Container(
|
return Container(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue