feat(albums): soft-delete albums (admin+owner, hidden & irreversible, both app entries)

契约 deleteAlbum(DELETE /albums/{albumId} → 204 幂等)+ status enum 'deleted':
- 服务端:softDelete(canView 404→canManage 403 防枚举;乐观锁 claim +
  outbox album.updated priority=high + audit album.delete);列表/详情/更新/
  成员/资产/分类/水印/照片/展示位绑定全部 deleted→404;公开端 cn
  (status!=='active')与 ov(isAlbumVisibleOv)天然屏蔽;ov 副本经
  album.updated 载荷 status 直投自动下线
- Admin:列表表格操作「删除」+ 详情页头「删除相册」(确认含不可恢复说明)
- App:操作面板「删除相册」(红字、二次确认、成功退回列表并刷新)
- 验证:API 56/56、App 397/397(analyze 0)、admin typecheck/lint/test/build 绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-23 10:13:08 +08:00
parent 9cb960c27a
commit 8e674a902e
31 changed files with 995 additions and 258 deletions
+113 -16
View File
@@ -32,7 +32,13 @@
"navUploads": "Uploads",
"navSettings": "Settings",
"badgeUploading": "{count} tasks",
"@badgeUploading": { "placeholders": { "count": { "type": "int" } } },
"@badgeUploading": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"albumUploadsOn": "Uploads on",
"albumUploadsOff": "Uploads off",
"albumPublicOff": "Not public",
@@ -116,7 +122,13 @@
"actionEdit": "Edit",
"membersTitle": "Members",
"membersTitleCount": "Members ({count})",
"@membersTitleCount": { "placeholders": { "count": { "type": "int" } } },
"@membersTitleCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"ownerGroup": "Owner",
"collaboratorsGroup": "Collaborators",
"noCollaborators": "No collaborators yet",
@@ -127,7 +139,13 @@
"newCategory": "New category",
"editCategory": "Edit category",
"deleteCategoryConfirm": "Delete \"{name}\"? Its photos will become uncategorized.",
"@deleteCategoryConfirm": { "placeholders": { "name": { "type": "String" } } },
"@deleteCategoryConfirm": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"noCategories": "No categories yet",
"albumCategories": "Categories",
"shareQrTitle": "Share QR code",
@@ -143,7 +161,13 @@
"memberInvited": "Invited",
"removeMember": "Remove member",
"removeMemberConfirm": "Remove {name} from this album?",
"@removeMemberConfirm": { "placeholders": { "name": { "type": "String" } } },
"@removeMemberConfirm": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"roleOwner": "Owner",
"roleCollaborator": "Collaborator",
"selfMarker": " (you)",
@@ -183,7 +207,13 @@
"queueSummaryWaiting": "waiting",
"queueSummaryNeedAttention": "need attention",
"queueRowUploadingPercent": "Uploading {percent}%",
"@queueRowUploadingPercent": { "placeholders": { "percent": { "type": "int" } } },
"@queueRowUploadingPercent": {
"placeholders": {
"percent": {
"type": "int"
}
}
},
"queueRowConfirming": "Confirming upload",
"queueRowWaitingUpload": "Waiting to upload",
"queueRowPreparing": "Preparing photo",
@@ -202,9 +232,21 @@
"queueEmpty": "No upload tasks",
"queueRetryAllFailed": "Retry all failed",
"queueAttemptCount": "{count} attempt(s)",
"@queueAttemptCount": { "placeholders": { "count": { "type": "int" } } },
"@queueAttemptCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"captureQueued": "{count} queued",
"@captureQueued": { "placeholders": { "count": { "type": "int" } } },
"@captureQueued": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"captureFlipCamera": "Switch camera",
"captureFlash": "Flash",
"uploadsHistory": "History",
@@ -255,17 +297,41 @@
"captureCategoryDefault": "Default category",
"captureSwitchHint": "Switching only affects photos captured afterwards",
"capturePendingBadge": "Pending {count}",
"@capturePendingBadge": { "placeholders": { "count": { "type": "int" } } },
"@capturePendingBadge": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"captureFailedBadge": "Failed {count}",
"@captureFailedBadge": { "placeholders": { "count": { "type": "int" } } },
"@captureFailedBadge": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"capturePickFromGallery": "Pick from gallery",
"captureShutter": "Capture",
"captureQueuedBadge": "Queued {count}",
"@captureQueuedBadge": { "placeholders": { "count": { "type": "int" } } },
"@captureQueuedBadge": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"captureEnqueuedFeedback": "Added to queue",
"captureExitTitle": "Leave shooting?",
"captureExitBody": "{count} photo(s) are still uploading. Tasks will continue in the background after you leave.",
"@captureExitBody": { "placeholders": { "count": { "type": "int" } } },
"@captureExitBody": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"captureKeepShooting": "Keep shooting",
"captureExitConfirm": "Leave",
"cameraPermissionDenied": "Camera unavailable. Allow camera access in system settings",
@@ -274,7 +340,13 @@
"uploadActionPick": "Pick from gallery",
"uploadAction": "Upload",
"uploadEnqueuedCount": "Added {count} photo(s) to the upload queue",
"@uploadEnqueuedCount": { "placeholders": { "count": { "type": "int" } } },
"@uploadEnqueuedCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"uploadPickNone": "No photos selected",
"albumManagePhotos": "Manage photos",
"albumTakeFirstPhoto": "Take the first photo",
@@ -309,16 +381,41 @@
"readonlyMember": "You can browse photos shared by others",
"moreActions": "More actions",
"photosTitle": "{count} photos",
"@photosTitle": { "placeholders": { "count": { "type": "int" } } },
"@photosTitle": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"photoUploadedBy": "Uploaded by {name}",
"@photoUploadedBy": { "placeholders": { "name": { "type": "String" } } },
"@photoUploadedBy": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"photoDimensions": "{width}×{height}",
"@photoDimensions": { "placeholders": { "width": { "type": "int" }, "height": { "type": "int" } } },
"@photoDimensions": {
"placeholders": {
"width": {
"type": "int"
},
"height": {
"type": "int"
}
}
},
"photoStatus": "Status",
"photoHideAction": "Hide photo",
"photoPublishAction": "Publish again",
"shareScanHint": "Scan to view photos",
"shareNotPublic": "This link is not publicly accessible yet",
"hidePhotoTitle": "Hide this photo?",
"hidePhotoBody": "Viewers will no longer see this photo. Authorized album managers can publish it again."
"hidePhotoBody": "Viewers will no longer see this photo. Authorized album managers can publish it again.",
"deleteAlbum": "Delete album",
"deleteAlbumConfirmTitle": "Delete this album?",
"deleteAlbumConfirmBody": "The album becomes invisible immediately and cannot be restored. Data is retained; recovery requires operations.",
"albumDeleted": "Album deleted"
}