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((ref) => PhotoApi(ref.watch(dioProvider))); /// 照片存档接口。 /// /// App 拍完照把**原图**传上来;水印是本地合成的,和这里无关。 /// 上传是「锦上添花」—— 失败了不该影响用户继续编辑和保存照片, /// 所以调用方要自己吞掉异常并给个「重试」入口。 class PhotoApi { PhotoApi(this._dio); final Dio _dio; /// 上传一张照片,返回后端记录 id Future 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( '/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)}'; } }