eff3526840
- `Pet.avatarUrl` 入模型;列表卡与详情头像改用真实预签名 URL(无图仍回退 爪印占位——服务端对非 ready 的 asset 直接给 null,客户端只有一种占位)。 - 详情页铅笔角标自此有功能:接 `MediaUploader`(purpose=pet_avatar);已有 头像时先给「更换 / 移除」二选一,移除即三态显式 null。 - 权限按 **WRITE 档**呈现(ADR-022 D3.5-3:owner + caregiver 可改头像, viewer 无入口),与资料编辑的 MANAGE 档刻意不同。因此纯头像 PATCH 只发 `version` + `avatarAssetId`,**不夹带任何资料字段**——夹带会让服务端按更严 的一半定档,caregiver 立刻 403。 - `UpdatePetRequest.avatarAssetId` 是该 schema 唯一的三态字段,其余字段保持 M2 两态语义(它们的 CHECK 约束本就不允许空值,「清空」无意义)。 - 40902 冲突后重取档案让用户自行决定重来,不静默重放(头像是用户可见的 覆盖操作,不该自动生效两次)。 测试 588 → 597(+9)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
3.4 KiB
Dart
75 lines
3.4 KiB
Dart
/// 宠物档案展示文案的纯函数集合(列表 / 详情 / 表单共用,可单测)。
|
||
library;
|
||
|
||
import 'package:patbond_flutter/core/network/api_exception.dart';
|
||
import 'package:patbond_flutter/features/pets/pet_models.dart';
|
||
|
||
String petSpeciesLabel(PetSpecies species) => switch (species) {
|
||
PetSpecies.dog => '狗狗',
|
||
PetSpecies.cat => '猫咪',
|
||
PetSpecies.other => '其他',
|
||
};
|
||
|
||
String petSexLabel(PetSex sex) => switch (sex) {
|
||
PetSex.male => '男孩',
|
||
PetSex.female => '女孩',
|
||
PetSex.unknown => '性别未知',
|
||
};
|
||
|
||
String petStatusLabel(PetStatus status) => switch (status) {
|
||
PetStatus.active => '正常',
|
||
PetStatus.lost => '走失中',
|
||
PetStatus.deceased => '已离世',
|
||
PetStatus.archived => '已归档',
|
||
};
|
||
|
||
/// 品种展示:目录品种名 > 自定义品种名 > 未知(Pet 响应恰有其一非空,
|
||
/// 双空为兜底)。
|
||
String petBreedLabel(Pet pet) =>
|
||
pet.breedDisplayName ?? pet.customBreedName ?? '品种未知';
|
||
|
||
/// 年龄:满一岁按「N 岁」、不满一岁按「N 个月」、当月新生「未满月」;
|
||
/// 生日缺席或晚于今天为「年龄未知」。
|
||
String petAgeLabel(DateTime? birthDate, {DateTime? now}) {
|
||
if (birthDate == null) return '年龄未知';
|
||
final today = now ?? DateTime.now();
|
||
var months =
|
||
(today.year - birthDate.year) * 12 + today.month - birthDate.month;
|
||
if (today.day < birthDate.day) months--;
|
||
if (months < 0) return '年龄未知';
|
||
if (months == 0) return '未满月';
|
||
if (months < 12) return '$months 个月';
|
||
return '${months ~/ 12} 岁';
|
||
}
|
||
|
||
/// 列表卡 / 详情头的元信息行:「柴犬 · 男孩 · 2 岁」。
|
||
String petMetaLine(Pet pet, {DateTime? now}) =>
|
||
'${petBreedLabel(pet)} · ${petSexLabel(pet.sex)} · '
|
||
'${petAgeLabel(pet.birthDate, now: now)}';
|
||
|
||
/// 加载失败的用户文案(一迭代三层错误模型:不可归属字段的加载错误
|
||
/// 走横幅;服务端原始 message 不上屏)。
|
||
String petLoadErrorMessage(ApiException? error) => switch (error) {
|
||
ApiNetworkException _ => '网络异常,请检查网络后重试',
|
||
ApiRateLimitException _ => '请求过于频繁,请稍后再试',
|
||
_ => '加载失败,请稍后重试',
|
||
};
|
||
|
||
/// 宠物头像写入失败的文案(T3.5-09)。按契约 v1.4.0 为
|
||
/// `PATCH /pets/{petId}` 新增的两格分层:
|
||
///
|
||
/// - 404/40405:asset 不存在 / 非本人 / 已删 / **用途不符**(帖图或用户头像
|
||
/// 当宠物头像)→ 引导重新上传,而不是「宠物不存在」(40401 才是那个)。
|
||
/// - 422/42203:本人的 `pet_avatar` asset 仍在 uploading/failed → 可重试。
|
||
/// - 403/40300:viewer 改头像(入口本已按 myRole 隐藏,此处兜底防越权构造)。
|
||
/// - 409/40902:版本冲突 → 由调用方重取档案后重试。
|
||
String petAvatarSaveErrorMessage(ApiException? error) => switch (error) {
|
||
ApiBusinessException(code: ApiCodes.mediaNotFound) => '头像已失效,请重新上传',
|
||
ApiBusinessException(code: ApiCodes.mediaNotReady) => '头像还没上传完,请稍后重试',
|
||
ApiBusinessException(code: ApiCodes.petAccessDenied) => '你没有修改该宠物头像的权限',
|
||
ApiBusinessException(code: ApiCodes.versionConflict) => '档案已被更新,请重新操作',
|
||
ApiNetworkException _ => '网络异常,请检查网络后重试',
|
||
ApiRateLimitException _ => '请求过于频繁,请稍后再试',
|
||
_ => '头像保存失败,请稍后重试',
|
||
};
|