Files
Aq-Accounting-Flutter/lib/api/photo_api.dart
T
2026-09-28 09:59:43 +08:00

75 lines
2.4 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/network/api_exception.dart';
import '../core/network/dio_client.dart';
final photoApiProvider = Provider<PhotoApi>((ref) => PhotoApi(ref.watch(dioProvider)));
/// 照片存档接口。
///
/// App 拍完照把**原图**传上来;水印是本地合成的,和这里无关。
/// 上传是「锦上添花」—— 失败了不该影响用户继续编辑和保存照片,
/// 所以调用方要自己吞掉异常并给个「重试」入口。
class PhotoApi {
PhotoApi(this._dio);
final Dio _dio;
/// 上传一张照片,返回后端记录 id
Future<int?> upload({
required String filePath,
double? latitude,
double? longitude,
String? address,
String? weather,
String? remark,
DateTime? takenTime,
}) async {
try {
final form = FormData.fromMap({
'file': await MultipartFile.fromFile(
filePath,
filename: _fileName(filePath),
),
'latitude': ?latitude,
'longitude': ?longitude,
if (address != null && address.isNotEmpty) 'address': address,
if (weather != null && weather.isNotEmpty) 'weather': weather,
if (remark != null && remark.isNotEmpty) 'remark': remark,
if (takenTime != null) 'takenTime': _formatTime(takenTime),
});
final res = await _dio.post<dynamic>(
'/photos',
data: form,
// 原图可能有好几 MB,移动网络下给宽一点
options: Options(receiveTimeout: const Duration(seconds: 60)),
);
final data = res.data;
if (data is Map) {
final id = data['id'];
if (id is int) return id;
}
return null;
} on DioException catch (e) {
final err = e.error;
throw err is ApiException ? err : ApiException(-1, '照片上传失败');
}
}
/// 从路径里取个带扩展名的文件名,后端靠它判断文件类型
static String _fileName(String path) {
final normalized = path.replaceAll('\\', '/');
final name = normalized.substring(normalized.lastIndexOf('/') + 1);
return name.isEmpty ? 'photo.jpg' : name;
}
static String _formatTime(DateTime time) {
String two(int n) => n.toString().padLeft(2, '0');
return '${time.year}-${two(time.month)}-${two(time.day)} '
'${two(time.hour)}:${two(time.minute)}:${two(time.second)}';
}
}