feat(feedback): implement user feedback collection system with email reporting

Backend:
- Add user_feedback table with RLS policy
- Create feedback submission API (multipart/form-data)
- Implement xlsx report generation with embedded images
- Add scheduled email delivery via Feishu SMTP
- Create HTML email templates (daily_report, no_feedback)

Frontend:
- Add feedback screen with type selection and image picker
- Support anonymous submission via skipAuth flag
- Collect device info and app version

Protocol:
- Document feedback API contract and error codes
- Update http-error-codes.md with FEEDBACK_* codes
This commit is contained in:
qzl
2026-04-20 12:49:54 +08:00
parent 913ed26f8d
commit 6a2a9d2c87
46 changed files with 4768 additions and 9 deletions
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../../../../data/network/api_client.dart';
import '../../../../l10n/app_localizations.dart';
import '../../../../shared/theme/design_tokens.dart';
import '../../../../shared/widgets/app_modal_dialog.dart';
@@ -9,6 +10,7 @@ import '../../data/repositories/invite_repository.dart';
import 'account_delete_screen.dart';
import '../widgets/settings_section_widgets.dart';
import 'coin_center_screen.dart';
import 'feedback_screen.dart';
import 'general_settings_screen.dart';
import 'invite_screen.dart';
import 'legal_center_screen.dart';
@@ -21,6 +23,7 @@ class SettingsScreen extends StatefulWidget {
required this.settings,
required this.coinBalance,
required this.inviteRepository,
required this.apiClient,
required this.onInterfaceLanguageChanged,
required this.onSettingsChanged,
required this.onUploadAvatar,
@@ -33,6 +36,7 @@ class SettingsScreen extends StatefulWidget {
final ProfileSettingsV1 settings;
final int coinBalance;
final InviteRepository inviteRepository;
final ApiClient apiClient;
final Future<void> Function(String languageTag) onInterfaceLanguageChanged;
final Future<void> Function(ProfileSettingsV1 settings) onSettingsChanged;
final Future<ProfileSettingsV1> Function(String filePath) onUploadAvatar;
@@ -125,6 +129,23 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
],
),
const SizedBox(height: AppSpacing.xl),
SettingsGroupCard(
children: [
SettingsMenuTile(
icon: Icons.feedback_outlined,
title: l10n.settingsFeedbackTitle,
tint: colors.primary,
background: colors.surfaceContainerHighest,
showDivider: false,
onTap: () => Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (_) => FeedbackScreen(apiClient: widget.apiClient),
),
),
),
],
),
SettingsGroupCard(
children: [
SettingsMenuTile(