feat(client): 反馈 Bug/功能建议 改为直接提交后台(文字+附图)

- feedback_repository + provider:图片上传 /feedback/images、提交 /feedback
- 关于我们的反馈入口改为表单弹窗:多行文字 + 选图上传(缩略图/可删,最多9张),
  提交到后台,不再走 mailto
- 平台/版本随提交上报(web 安全:defaultTargetPlatform,不引 dart:io)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-05 21:31:46 +08:00
parent 7a1d8465e5
commit 36e5677b7c
3 changed files with 267 additions and 27 deletions
@@ -0,0 +1,78 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../core/api/api_client.dart';
import '../core/exceptions.dart';
class FeedbackRepository {
final ApiClient _client;
const FeedbackRepository(this._client);
/// 上传单张图片,返回服务器相对 URL/images/feedback/...)。
Future<String> uploadImage({
Uint8List? bytes,
String? filePath,
String filename = 'image.jpg',
}) async {
try {
final MultipartFile file;
if (bytes != null) {
file = MultipartFile.fromBytes(bytes, filename: filename);
} else {
file = await MultipartFile.fromFile(filePath!);
}
final formData = FormData.fromMap({'file': file});
final resp = await _client.post('/feedback/images', data: formData);
return (resp.data as Map<String, dynamic>)['url'] as String;
} on DioException catch (e) {
throw AppException(
e.response?.data?['error'] as String? ?? '图片上传失败',
statusCode: e.response?.statusCode,
);
}
}
/// 提交反馈(bug / suggestion),文字 + 图片 URL 列表。
Future<void> submit({
required String type,
required String content,
required List<String> images,
}) async {
String appVersion = '';
try {
appVersion = (await PackageInfo.fromPlatform()).version;
} catch (_) {}
try {
await _client.post('/feedback', data: {
'type': type,
'content': content,
'images': images,
'app_version': appVersion,
'platform': _platform(),
});
} on DioException catch (e) {
throw AppException(
e.response?.data?['error'] as String? ?? '提交失败',
statusCode: e.response?.statusCode,
);
}
}
String _platform() {
if (kIsWeb) return 'web';
switch (defaultTargetPlatform) {
case TargetPlatform.windows:
return 'windows';
case TargetPlatform.macOS:
return 'macos';
case TargetPlatform.android:
return 'android';
case TargetPlatform.iOS:
return 'ios';
case TargetPlatform.linux:
return 'linux';
default:
return 'unknown';
}
}
}