水印优化+ORC优化
This commit is contained in:
@@ -30,6 +30,17 @@ double asDouble(Object? value, {double fallback = 0}) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// 取可空小数:null / 无法解析都返回 null
|
||||
///
|
||||
/// 用于照片的经纬度这类"没有值"本身有含义的字段
|
||||
/// (定位没拿到就没有,不能退化成 0 —— 0 是几内亚湾)
|
||||
double? asNullableDouble(Object? value) {
|
||||
if (value == null) return null;
|
||||
if (value is num) return value.toDouble();
|
||||
if (value is String) return double.tryParse(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
String asString(Object? value, {String fallback = ''}) {
|
||||
if (value == null) return fallback;
|
||||
return value.toString();
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import 'json_util.dart';
|
||||
import 'watermark.dart';
|
||||
|
||||
/// 一张已上传的照片。
|
||||
///
|
||||
/// ⚠️ [url] 指向的是**未加水印的原图** —— 服务器只存原图,水印一直是
|
||||
/// App 本地合成的。所以「重新打水印」(换颜色、换位置、补备注)随时可行。
|
||||
class PhotoItem {
|
||||
const PhotoItem({
|
||||
required this.id,
|
||||
required this.url,
|
||||
required this.thumbUrl,
|
||||
required this.takenAt,
|
||||
required this.address,
|
||||
required this.weather,
|
||||
required this.remark,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
});
|
||||
|
||||
final int id;
|
||||
|
||||
/// 原图地址(相对路径,如 `/uploads/photo/2026/09/28/xxx.jpg`)
|
||||
final String url;
|
||||
|
||||
/// 缩略图地址。为 null 表示后端没生成出来,客户端只能回退用原图。
|
||||
final String? thumbUrl;
|
||||
|
||||
/// 拍摄时间,已格式化;后端两个时间字段取到哪个用哪个
|
||||
final String takenAt;
|
||||
|
||||
final String address;
|
||||
|
||||
/// 「晴 25℃」这种拼好的天气文本
|
||||
final String weather;
|
||||
|
||||
final String remark;
|
||||
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
|
||||
factory PhotoItem.fromJson(Map<String, dynamic> json) {
|
||||
// 拍摄时间是客户端传的,可能为空(格式没对上时后端会存 null),
|
||||
// 那就退回记录创建时间 —— 至少能显示个日期
|
||||
final taken = formatDateTime(json['takenTime']);
|
||||
final created = formatDateTime(json['createTime']);
|
||||
|
||||
return PhotoItem(
|
||||
id: asInt(json['id']),
|
||||
url: asString(json['url']),
|
||||
thumbUrl: asString(json['thumbUrl']).isEmpty
|
||||
? null
|
||||
: asString(json['thumbUrl']),
|
||||
takenAt: taken.isNotEmpty ? taken : created,
|
||||
address: asString(json['address']),
|
||||
weather: asString(json['weather']),
|
||||
remark: asString(json['remark']),
|
||||
latitude: asNullableDouble(json['latitude']),
|
||||
longitude: asNullableDouble(json['longitude']),
|
||||
);
|
||||
}
|
||||
|
||||
/// 还原成水印数据,「重新打水印」时用。
|
||||
///
|
||||
/// 关键点是**复用这张照片当时的地址和天气**,而不是拿当前位置 ——
|
||||
/// 不然给三年前的照片重新加个水印,地点会变成「你现在站的地方」。
|
||||
WatermarkData toWatermarkData() {
|
||||
final split = splitWeatherText(weather);
|
||||
return WatermarkData(
|
||||
time: parsePhotoTime(takenAt),
|
||||
address: address,
|
||||
weather: split.weather,
|
||||
temperature: split.temperature,
|
||||
remark: remark,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 把后端存的 `yyyy-MM-dd HH:mm` 解析回 DateTime;解析不了就用当前时间
|
||||
DateTime parsePhotoTime(String value) {
|
||||
final normalized = value.trim().replaceFirst(' ', 'T');
|
||||
return DateTime.tryParse(normalized) ?? DateTime.now();
|
||||
}
|
||||
|
||||
/// 拆开后端存的天气文本(「晴 25℃」→ weather=晴, temperature=25℃)。
|
||||
///
|
||||
/// 存的时候是一个字段(上传时客户端就是这么拼的),所以这里拆回来。
|
||||
/// 两种情况都要兜住:只有天气没有温度(「晴」)、只有温度没有天气(「25℃」)。
|
||||
({String weather, String temperature}) splitWeatherText(String combined) {
|
||||
final text = combined.trim();
|
||||
if (text.isEmpty) return (weather: '', temperature: '');
|
||||
|
||||
final parts = text.split(' ');
|
||||
if (parts.last.endsWith('℃')) {
|
||||
return (
|
||||
weather: parts.sublist(0, parts.length - 1).join(' '),
|
||||
temperature: parts.last,
|
||||
);
|
||||
}
|
||||
return (weather: text, temperature: '');
|
||||
}
|
||||
|
||||
/// 照片分页
|
||||
class PhotoPage {
|
||||
const PhotoPage({
|
||||
required this.items,
|
||||
required this.total,
|
||||
required this.hasMore,
|
||||
});
|
||||
|
||||
final List<PhotoItem> items;
|
||||
final int total;
|
||||
final bool hasMore;
|
||||
|
||||
static const empty = PhotoPage(items: [], total: 0, hasMore: false);
|
||||
|
||||
factory PhotoPage.fromJson(Map<String, dynamic> json) {
|
||||
return PhotoPage(
|
||||
items: asMapList(json['list']).map(PhotoItem.fromJson).toList(),
|
||||
total: asInt(json['total']),
|
||||
hasMore: json['hasMore'] == true,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user