feat: 实现用户画像、占卜历史与后端用户管理模块

This commit is contained in:
ZL-Q
2026-04-06 01:28:10 +08:00
parent d87b2e1e3a
commit 8a18b3528b
77 changed files with 5850 additions and 2604 deletions
@@ -120,9 +120,20 @@ class SettingsMenuTile extends StatelessWidget {
}
class ProfileHeaderCard extends StatelessWidget {
const ProfileHeaderCard({super.key, required this.account});
const ProfileHeaderCard({
super.key,
required this.account,
required this.displayName,
required this.bio,
required this.avatarUrl,
required this.onEditTap,
});
final String account;
final String displayName;
final String bio;
final String? avatarUrl;
final VoidCallback onEditTap;
@override
Widget build(BuildContext context) {
@@ -137,22 +148,83 @@ class ProfileHeaderCard extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(AppSpacing.lg),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 28,
backgroundColor: colors.surfaceContainerHighest,
child: Icon(Icons.person_rounded, color: colors.primary),
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(AppRadius.full),
),
alignment: Alignment.center,
child: _AvatarContent(avatarUrl: avatarUrl),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(account, style: Theme.of(context).textTheme.titleMedium),
Text(
displayName,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: AppSpacing.xs),
Text(
account,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colors.onSurfaceVariant,
),
),
if (bio.isNotEmpty) ...[
const SizedBox(height: AppSpacing.sm),
Text(
bio,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium,
),
],
],
),
),
Icon(Icons.edit_outlined, color: colors.outline, size: 20),
const SizedBox(width: AppSpacing.sm),
Material(
color: colors.surface,
elevation: 2,
shadowColor: colors.shadow.withValues(alpha: 0.35),
borderRadius: BorderRadius.circular(AppRadius.full),
child: InkWell(
onTap: onEditTap,
borderRadius: BorderRadius.circular(AppRadius.full),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(
color: colors.primary.withValues(alpha: 0.24),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.edit_rounded, color: colors.primary, size: 18),
const SizedBox(width: AppSpacing.xs),
Text(
AppLocalizations.of(context)!.settingsEditProfileAction,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: colors.primary,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
),
],
),
),
@@ -160,6 +232,32 @@ class ProfileHeaderCard extends StatelessWidget {
}
}
class _AvatarContent extends StatelessWidget {
const _AvatarContent({required this.avatarUrl});
final String? avatarUrl;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final url = avatarUrl?.trim() ?? '';
if (url.isNotEmpty) {
return ClipOval(
child: Image.network(
url,
width: 56,
height: 56,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(Icons.person_rounded, color: colors.primary, size: 30);
},
),
);
}
return Icon(Icons.person_rounded, color: colors.primary, size: 30);
}
}
class WalletHeroCard extends StatelessWidget {
const WalletHeroCard({
super.key,