feat(app): album banner picker in edit form — show current banner, pick & upload on save
- api_client:uploadAlbumAssetFromFile 泛化(cover 为薄壳)+ fetchAlbumAssetUrl
(GET /albums/{id}/assets → 指定 kind 全局行 URL)
- 编辑页 Details:封面下方新增「宣传图」3:1 选择器——编辑模式异步加载现有
banner 预览(失败静默);保存成功后上传(同封面:失败不阻塞表单结果)
- 未保存变更判断纳入 banner;l10n zh/en
- 测试适配:编辑用例 pumpForm 后清空 banner 现状预检请求(397/397 全绿)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -330,17 +330,48 @@ class ApiClient {
|
||||
/// 一步上传相册封面:选图 → presign → PUT → complete。
|
||||
/// [filePath] 是本机源文件路径(由 singleImagePicker 返回)。
|
||||
/// 返回封面短期 URL(仅用于即时预览;列表走 AlbumSummary.cover)。
|
||||
Future<String> uploadAlbumCoverFromFile(
|
||||
Future<String> uploadAlbumCoverFromFile(String albumId, String filePath) =>
|
||||
uploadAlbumAssetFromFile(
|
||||
albumId,
|
||||
kind: assetKindCover,
|
||||
filePath: filePath,
|
||||
);
|
||||
|
||||
/// 资产现状(listAlbumAssetStatus):指定 kind 的全局行(locale null)URL;
|
||||
/// 未设置返回 null。banner 预览用(语言行不区分——编辑页只管全局)。
|
||||
Future<String?> fetchAlbumAssetUrl(
|
||||
String albumId,
|
||||
String filePath,
|
||||
) async {
|
||||
String kind, {
|
||||
String? locale,
|
||||
}) async {
|
||||
final resp = await _send(
|
||||
() => _dio.get<Map<String, dynamic>>('/v1/albums/$albumId/assets'),
|
||||
);
|
||||
final items = (resp.data!['items'] as List<dynamic>)
|
||||
.cast<Map<String, dynamic>>();
|
||||
for (final item in items) {
|
||||
if (item['kind'] == kind && item['locale'] == locale) {
|
||||
return item['url'] as String;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 一步上传相册资产(banner/cover/logo):选图 → presign → PUT → complete。
|
||||
/// [filePath] 是本机源文件路径(由 singleImagePicker 返回)。
|
||||
/// 返回资产短期 URL(仅用于即时预览;现状经 [fetchAlbumAssetUrl] 查询)。
|
||||
Future<String> uploadAlbumAssetFromFile(
|
||||
String albumId, {
|
||||
required String kind,
|
||||
required String filePath,
|
||||
}) async {
|
||||
final file = File(filePath);
|
||||
final bytes = await file.length();
|
||||
final filename = filePath.split(Platform.pathSeparator).last;
|
||||
final contentType = _guessContentType(filename);
|
||||
final init = await initAlbumAsset(
|
||||
albumId,
|
||||
kind: assetKindCover,
|
||||
kind: kind,
|
||||
filename: filename,
|
||||
bytes: bytes,
|
||||
contentType: contentType,
|
||||
|
||||
Reference in New Issue
Block a user