feat: 任务轮询 + Token刷新容错 + 提交防抖 + 文件上传Hook (0.0.20)

### 任务状态轮询
- 新增 useTaskPolling hook:批量轮询非终态任务,递归 setTimeout + next_poll_ms 自适应间隔
- TaskHistory 集成批量轮询,OutputPreview 移除独立轮询改为状态转换监听
- batchTaskStatus 修复数组参数序列化(GET + 逗号拼接,避免 bracket 格式)
- 统一 POLLING_STATUSES 常量管理非终态状态

### Token 刷新容错
- 双轨刷新:主动续期(JWT exp 75% 时间点 setTimeout)+ 被动兜底(handle401)
- 刷新失败分级:永久失败(throw RefreshTokenExpiredError)→清token弹登录;临时失败(return null)→仅拒绝当前请求
- AppProvider 登录/自动登录/退出 启停主动刷新定时器

### 提交按钮防抖
- 2s 节流防抖("你点击的太快了")+ 二次确认弹窗("您已提交,确认要再次提交吗?")
- 修复 useAsyncMutation.execute 返回值判空(原 try/catch 死代码)

### 文件上传 Hook
- 新增 useFileUpload hook:封装 request.upload() 为 antd Upload 兼容 customRequest
- ImageInput/ImageListInput/AudioListInput 集成 customRequest
- uploadValidation beforeUpload 返回值修正(false→true)

### 其他
- AnnouncementDrawer 接入公告 API + EnumResolver
- billing/announcement/banner 服务模块完善
- CLAUDE.md 补充架构约定文档
This commit is contained in:
2026-06-12 19:03:36 +08:00
parent 4237dcbaf6
commit 9e8c5b7109
22 changed files with 787 additions and 149 deletions

View File

@@ -23,6 +23,8 @@ import {
setStoredRefreshToken,
clearStoredRefreshToken,
initRefreshTokenStore,
scheduleProactiveRefresh,
clearProactiveRefresh,
} from '@/services/auth-token';
import {initModelCache} from '@/services/cache/model-cache';
@@ -89,6 +91,8 @@ export function AppProvider({ children }: AppProviderProps) {
const login = useCallback(async (auth: LoginResponseBody) => {
await setToken(auth.access_token);
await setStoredRefreshToken(auth.refresh_token);
// 启动主动刷新定时器(在 access_token 过期前自动续期)
scheduleProactiveRefresh(auth.access_token);
setUser(auth);
setIsLoggedIn(true);
logger.info('auth', 'User logged in', {
@@ -102,6 +106,7 @@ export function AppProvider({ children }: AppProviderProps) {
logger.info('auth', 'User logged out');
clearToken();
clearStoredRefreshToken();
clearProactiveRefresh();
setUser(null);
setIsLoggedIn(false);
emit(EVENTS.LOGOUT);
@@ -133,6 +138,7 @@ export function AppProvider({ children }: AppProviderProps) {
if (aborted) return;
await setToken(res.access_token);
await setStoredRefreshToken(res.refresh_token);
scheduleProactiveRefresh(res.access_token);
setIsLoggedIn(true);
logger.info('auth', 'Auto-login succeeded');
// 补全用户信息