Browse Source
Add a plugin to enhance toast notifications with an autoclose fallback mechanism, ensuring that notifications are removed after a specified duration. This includes handling timers for toast removal and preventing duplicate plugin application. Made-with: Cursormain
3 changed files with 223 additions and 0 deletions
@ -0,0 +1,53 @@ |
|||||
|
const TOAST_PATCH_FLAG = '__person_panel_toast_patch_applied__' |
||||
|
const DEFAULT_TOAST_DURATION_MS = 5000 |
||||
|
const FALLBACK_EXTRA_MS = 400 |
||||
|
|
||||
|
export default defineNuxtPlugin(() => { |
||||
|
const g = globalThis as typeof globalThis & { [TOAST_PATCH_FLAG]?: boolean } |
||||
|
if (g[TOAST_PATCH_FLAG]) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const toast = useToast() |
||||
|
const originalAdd = toast.add.bind(toast) |
||||
|
const originalRemove = toast.remove.bind(toast) |
||||
|
type ToastId = Parameters<typeof toast.remove>[0] |
||||
|
const timers = new Map<ToastId, ReturnType<typeof setTimeout>>() |
||||
|
|
||||
|
const clearFallbackTimer = (id: ToastId) => { |
||||
|
const timer = timers.get(id) |
||||
|
if (!timer) { |
||||
|
return |
||||
|
} |
||||
|
clearTimeout(timer) |
||||
|
timers.delete(id) |
||||
|
} |
||||
|
|
||||
|
const scheduleFallbackRemove = (id: ToastId, duration?: number) => { |
||||
|
clearFallbackTimer(id) |
||||
|
|
||||
|
const finalDuration = typeof duration === 'number' ? duration : DEFAULT_TOAST_DURATION_MS |
||||
|
if (finalDuration <= 0) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const timer = setTimeout(() => { |
||||
|
originalRemove(id) |
||||
|
timers.delete(id) |
||||
|
}, finalDuration + FALLBACK_EXTRA_MS) |
||||
|
timers.set(id, timer) |
||||
|
} |
||||
|
|
||||
|
toast.add = ((input: Parameters<typeof toast.add>[0]) => { |
||||
|
const created = originalAdd(input) |
||||
|
scheduleFallbackRemove(created.id, created.duration) |
||||
|
return created |
||||
|
}) as typeof toast.add |
||||
|
|
||||
|
toast.remove = ((id: ToastId) => { |
||||
|
clearFallbackTimer(id) |
||||
|
originalRemove(id) |
||||
|
}) as typeof toast.remove |
||||
|
|
||||
|
g[TOAST_PATCH_FLAG] = true |
||||
|
}) |
||||
@ -0,0 +1,170 @@ |
|||||
|
# 导出功能复核与开发指引(2026-04-24) |
||||
|
|
||||
|
## 1. 文档目的 |
||||
|
|
||||
|
本文件用于后续迭代开发时快速理解“用户数据导出”功能的当前实现状态、行为边界与已知风险,避免重复摸索。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 2. 复核范围 |
||||
|
|
||||
|
本次复核覆盖: |
||||
|
|
||||
|
- 后端 API:创建 / 列表 / 下载 / 删除 |
||||
|
- 服务层:任务状态机、任务删除与目录清理、导出执行流程 |
||||
|
- 导出产物:`data/`、`files/`、`manifest.json` |
||||
|
- 前端导出中心交互:创建、刷新、下载、删除、重新导出 |
||||
|
- 关键测试可执行性 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 3. 关键代码位置 |
||||
|
|
||||
|
- API |
||||
|
- `server/api/me/export/request.post.ts` |
||||
|
- `server/api/me/export/tasks.get.ts` |
||||
|
- `server/api/me/export/tasks/[id]/download.get.ts` |
||||
|
- `server/api/me/export/tasks/[id].delete.ts` |
||||
|
- 服务层 |
||||
|
- `server/service/export/jobs.ts` |
||||
|
- `server/service/export/run.ts` |
||||
|
- `server/service/export/build-data.ts` |
||||
|
- `server/service/export/build-files.ts` |
||||
|
- `server/service/export/build-manifest.ts` |
||||
|
- 前端 |
||||
|
- `app/pages/me/export/index.vue` |
||||
|
- `app/types/export.ts` |
||||
|
- 测试 |
||||
|
- `server/service/export/jobs.test.ts` |
||||
|
- `server/service/export/build-manifest.test.ts` |
||||
|
- `server/utils/me-export-request-body.test.ts` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 4. 当前功能状态(复核结论) |
||||
|
|
||||
|
### 4.1 已实现并可用 |
||||
|
|
||||
|
- **任务生命周期** |
||||
|
- 支持 `queued -> running -> succeeded/failed/expired` |
||||
|
- 同一用户存在 `queued/running` 任务时,禁止再次创建(409) |
||||
|
|
||||
|
- **导出产物构建** |
||||
|
- 导出目录结构: |
||||
|
- `manifest.json` |
||||
|
- `data/*.json` |
||||
|
- `files/*` |
||||
|
- `manifest` 包含: |
||||
|
- `schemaVersion` |
||||
|
- `exportedAt/exportCutoffAt` |
||||
|
- `maskPolicy` |
||||
|
- `stats` |
||||
|
- `checksums.data/files` |
||||
|
|
||||
|
- **下载完整包** |
||||
|
- 下载接口返回完整压缩包(`tar.gz`) |
||||
|
- 通过 `tar -czf -` 流式返回,不落地额外压缩文件 |
||||
|
|
||||
|
- **任务列表自愈** |
||||
|
- 刷新任务列表时会检查已完成任务的导出文件可用性 |
||||
|
- 缺失/过期会自动标记为 `expired` 并写入错误信息 |
||||
|
|
||||
|
- **任务删除** |
||||
|
- `DELETE /api/me/export/tasks/:id` |
||||
|
- 非 `running` 任务可删 |
||||
|
- 删除时尝试清理对应 `.tmp/exports` 目录,并做路径边界保护 |
||||
|
|
||||
|
- **前端交互** |
||||
|
- 支持创建、刷新、下载、删除 |
||||
|
- `expired` 任务显示“重新导出”按钮 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 5. 实测结果(本地复核) |
||||
|
|
||||
|
执行命令: |
||||
|
|
||||
|
```bash |
||||
|
bun test server/service/export/jobs.test.ts |
||||
|
bun test server/service/export/build-manifest.test.ts |
||||
|
bun test server/utils/me-export-request-body.test.ts |
||||
|
``` |
||||
|
|
||||
|
结果: |
||||
|
|
||||
|
- `server/service/export/jobs.test.ts`:通过(12 pass) |
||||
|
- `server/service/export/build-manifest.test.ts`:通过(1 pass) |
||||
|
- `server/utils/me-export-request-body.test.ts`:失败(`Cannot find package 'h3'`) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 6. 已知问题与风险 |
||||
|
|
||||
|
### R1(中):`me-export-request-body` 单测依赖问题 |
||||
|
|
||||
|
- 现象:测试环境无法解析 `h3`,导致 `server/utils/me-export-request-body.test.ts` 失败。 |
||||
|
- 影响:该 util 的单测在当前环境不稳定,CI/新开发机可能复现。 |
||||
|
- 建议: |
||||
|
- 方案 A:`me-export-request-body.ts` 改为抛项目通用错误对象(`{ statusCode, statusMessage }`),不直接依赖 `h3`。 |
||||
|
- 方案 B:明确把 `h3` 作为可解析依赖并固定版本,确保测试环境一致。 |
||||
|
|
||||
|
### R2(中):下载包格式依赖系统 `tar` |
||||
|
|
||||
|
- 当前下载使用子进程 `tar` 打包,依赖运行环境存在 `tar` 命令。 |
||||
|
- 建议: |
||||
|
- 在部署文档里明确依赖项; |
||||
|
- 或改为 Node 库打包,避免系统命令依赖。 |
||||
|
|
||||
|
### R3(中):下载失败时状态修正为 `expired` 的语义边界 |
||||
|
|
||||
|
- 当前将“文件缺失/打包失败/过期”统一落到 `expired`。 |
||||
|
- 建议: |
||||
|
- 若需更细粒度运维排障,可新增状态或错误码区分(如 `EXPORT_MISSING`、`EXPORT_PACKAGE_FAILED`)。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 7. 对外接口行为(当前版本) |
||||
|
|
||||
|
### 7.1 创建任务 |
||||
|
|
||||
|
- `POST /api/me/export/request` |
||||
|
- body:`{ "maskPolicy": "masked" | "raw" }`(可省略,默认 `masked`) |
||||
|
- 返回:`taskId`, `status` |
||||
|
|
||||
|
### 7.2 获取任务列表 |
||||
|
|
||||
|
- `GET /api/me/export/tasks` |
||||
|
- 返回当前用户任务 |
||||
|
- 返回前会自动做“已完成任务文件存在性检查”,必要时改为 `expired` |
||||
|
|
||||
|
### 7.3 下载任务产物 |
||||
|
|
||||
|
- `GET /api/me/export/tasks/:id/download` |
||||
|
- 仅允许下载本人且 `succeeded` 的任务 |
||||
|
- 返回完整 `tar.gz` 包 |
||||
|
- 常见错误: |
||||
|
- `409`:任务未完成 |
||||
|
- `410`:任务过期或文件丢失(并同步标记为 `expired`) |
||||
|
|
||||
|
### 7.4 删除任务 |
||||
|
|
||||
|
- `DELETE /api/me/export/tasks/:id` |
||||
|
- 仅允许删除本人任务 |
||||
|
- `running` 任务返回 `409` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 8. 后续开发建议(按优先级) |
||||
|
|
||||
|
1. **修复 R1**:先解决 `h3` 解析导致的单测不稳定。 |
||||
|
2. **补 API 级测试**:增加 `request/tasks/download/delete` 的集成测试。 |
||||
|
3. **下载格式可配置**:支持 `zip`(前端兼容性更好)或可选 `tar.gz/zip`。 |
||||
|
4. **导出清理任务**:增加后台任务,定期清理已过期目录与旧任务数据。 |
||||
|
5. **更细状态与错误码**:提高运维可观测性。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 9. 最终结论 |
||||
|
|
||||
|
- **功能可用性**:通过(可用于后续开发迭代) |
||||
|
- **工程稳定性**:有条件通过(需优先修复 R1) |
||||
Binary file not shown.
Loading…
Reference in new issue