Files
lixi eff3526840 新增:宠物头像接线——铅笔角标接上传、列表与详情展示真实头像(T3.5-09)
- `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>
2026-09-11 15:37:25 +08:00

75 lines
3.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 宠物档案展示文案的纯函数集合(列表 / 详情 / 表单共用,可单测)。
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/40405asset 不存在 / 非本人 / 已删 / **用途不符**(帖图或用户头像
/// 当宠物头像)→ 引导重新上传,而不是「宠物不存在」(40401 才是那个)。
/// - 422/42203:本人的 `pet_avatar` asset 仍在 uploading/failed → 可重试。
/// - 403/40300viewer 改头像(入口本已按 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 _ => '请求过于频繁,请稍后再试',
_ => '头像保存失败,请稍后重试',
};