import '../config/app_config.dart'; /// 与 Web `pickAvatarFromRecord` 对齐:头像 URL 可能在多字段或嵌套对象中。 String? pickAvatarUrlFromRecord(Map? raw) { if (raw == null) { return null; } const directKeys = [ 'avatar', 'headImg', 'head_img', 'headPortrait', 'head_portrait', 'portrait', 'photo', 'photoUrl', 'photo_url', 'faceUrl', 'face_url', 'icon', 'userAvatar', 'user_avatar', 'avatarUrl', 'avatarURL', 'logo', ]; for (final k in directKeys) { final v = raw[k]; if (v != null) { final s = v.toString().trim(); if (s.isNotEmpty && s != 'null' && s != 'undefined') { return s; } } } const nestedKeys = [ 'data', 'member', 'user', 'account', 'result', 'userInfo', 'user_info', ]; for (final nk in nestedKeys) { final nested = raw[nk]; if (nested is Map) { final found = pickAvatarUrlFromRecord( Map.from(nested), ); if (found != null) { return found; } } } return null; } /// 将站内相对头像路径解析为可用绝对 URL(与 Web 外链 OSS 同源策略下的展示前提一致)。 String? normalizeAvatarHttpUrl(String? raw) { if (raw == null) { return null; } final s = raw.trim(); if (s.isEmpty || s == 'null' || s == 'undefined') { return null; } final lower = s.toLowerCase(); if (lower.startsWith('http://') || lower.startsWith('https://')) { return s; } if (s.startsWith('//')) { return 'https:$s'; } try { final baseUri = Uri.parse(AppConfig.effectiveApiBaseUrl); return baseUri.resolve(s).toString(); } catch (_) { return s; } } /// pick + normalize,供界面与列表模型直接使用。 String? resolvedAvatarUrlFromRecord(Map? raw) { return normalizeAvatarHttpUrl(pickAvatarUrlFromRecord(raw)); }