feat(admin): album management pages with members and categories

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-18 12:33:46 +08:00
parent a6cc190751
commit 50c59ca9d4
27 changed files with 2977 additions and 16 deletions
@@ -0,0 +1,78 @@
/**
* 资产预签名上传状态机(纯函数 reducer)
*
* 三段链路 init(POST /assets 预签名)→ PUT(staging 直传)→ complete
* (核验生效并返回签名 URL)。任何一步失败进入 error(携带阶段与原因,
* 可重新开始);complete 幂等,成功态保留签名 URL 与过期时间用于预览。
* 抽成纯 reducer 以便单测与 UI 解耦。
*/
export type AssetUploadStage =
'idle' | 'initializing' | 'uploading' | 'completing' | 'done' | 'error'
export interface AssetUploadState {
stage: AssetUploadStage
/** 失败发生的阶段(error 态定位重试入口) */
failedStage?: 'init' | 'put' | 'complete'
/** 错误消息(已本地化/服务端文案) */
error?: string
/** complete 返回的签名预览 URL */
previewUrl?: string
/** 签名 URL 过期时间(ISO) */
previewExpiresAt?: string
}
export type AssetUploadEvent =
| { type: 'START' }
| { type: 'INIT_OK' }
| { type: 'PUT_OK' }
| { type: 'COMPLETE_OK'; url: string; expiresAt: string }
| { type: 'FAIL'; stage: 'init' | 'put' | 'complete'; message?: string }
| { type: 'RESET' }
export const INITIAL_ASSET_UPLOAD_STATE: AssetUploadState = { stage: 'idle' }
/**
* 状态转移:非法转移(如 idle 收到 PUT_OK)保持原状——UI 侧的迟到响应
* 不得污染已重置的新会话。
*/
export function nextAssetUploadState(
state: AssetUploadState,
event: AssetUploadEvent
): AssetUploadState {
switch (event.type) {
case 'START':
if (state.stage === 'idle' || state.stage === 'done' || state.stage === 'error') {
return { stage: 'initializing' }
}
return state
case 'INIT_OK':
if (state.stage === 'initializing') return { stage: 'uploading' }
return state
case 'PUT_OK':
if (state.stage === 'uploading') return { stage: 'completing' }
return state
case 'COMPLETE_OK':
if (state.stage === 'completing') {
return {
stage: 'done',
previewUrl: event.url,
previewExpiresAt: event.expiresAt
}
}
return state
case 'FAIL':
if (
state.stage === 'initializing' ||
state.stage === 'uploading' ||
state.stage === 'completing'
) {
return { stage: 'error', failedStage: event.stage, error: event.message }
}
return state
case 'RESET':
return INITIAL_ASSET_UPLOAD_STATE
default:
return state
}
}