/** * 资产预签名上传状态机(纯函数 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 } }