106 lines
4.1 KiB
Markdown
106 lines
4.1 KiB
Markdown
# 实现方案 — 2026-04-16-20-46-50
|
||
|
||
## 根因分析
|
||
|
||
### 1. LocalStorage 容量超限(QuotaExceededError)
|
||
|
||
浏览器对单个域名的 `localStorage` 通常有 **5MB** 的严格容量限制。
|
||
|
||
当前代码在抽帧时使用了视频的原始分辨率:
|
||
```tsx
|
||
canvas.width = video.videoWidth;
|
||
canvas.height = video.videoHeight;
|
||
const dataUrl = canvas.toDataURL('image/jpeg', 0.9);
|
||
```
|
||
|
||
如果上传的是 1080p 甚至 4K 视频:
|
||
- 单张 0.9 质量 JPEG Base64 图片可能达到 **300KB ~ 1MB**;
|
||
- 自动提取 12 张关键帧 + 手动截图若干张,总数据量可能达到 **5MB ~ 10MB**;
|
||
- 直接超过 `localStorage` 的 5MB 上限。
|
||
|
||
### 2. 静默失败
|
||
|
||
`src/utils/storage.ts` 中的 `set` 方法:
|
||
```typescript
|
||
set<T>(key: string, value: T): void {
|
||
try {
|
||
localStorage.setItem(key, JSON.stringify(value));
|
||
} catch {
|
||
// ignore quota exceeded
|
||
}
|
||
}
|
||
```
|
||
|
||
当数据量超过 5MB 时,`localStorage.setItem` 抛出 `QuotaExceededError`,但被 `catch` 静默吞掉。
|
||
|
||
**实际发生的过程:**
|
||
1. 用户上传视频 → 此时 `videos` 数组中的 `url` 是 `blob:http://...`(短字符串),数据量很小,**保存成功**;
|
||
2. 系统开始自动抽帧,生成巨大的 Base64 `dataUrl` 数组;
|
||
3. 调用 `saveDraftToStorage()` 尝试保存时,`localStorage.setItem` 触发超限报错;
|
||
4. 异常被 `catch` 忽略,**draft 没有被更新**(或更新失败);
|
||
5. 当用户离开页面再返回时,localStorage 中读到的 draft 仍然停留在"仅有视频、没有关键帧"的状态。
|
||
|
||
这就是为什么:编辑器内容保留了,视频保留了,但**关键帧全部消失**。
|
||
|
||
## 修改方向
|
||
|
||
### 方向一:压缩关键帧分辨率与质量(快速修复,推荐优先执行)
|
||
|
||
关键帧只是用于插入报告的缩略图,通常不需要 4K 原画质。可以:
|
||
1. 设定最大宽度(如 800px),等比缩放 Canvas;
|
||
2. 将 JPEG 导出质量从 `0.9` 降到 `0.5 ~ 0.6`;
|
||
3. 这样单张图片体积可从 500KB 压缩到 30KB~80KB,十几张关键帧总计不到 1MB,远低于 5MB 限制。
|
||
|
||
**修改点:**
|
||
- `captureFrame()`(手动截图)
|
||
- `autoCaptureFrames()`(自动抽帧)
|
||
|
||
### 方向二:增加存储超限的可见性
|
||
|
||
在 `storage.ts` 中不再静默吞掉异常,而是至少输出 `console.error`,甚至可以在 UI 层捕获后提示用户:
|
||
"报告数据过大,请降低视频截图质量或删除部分图片。"
|
||
|
||
### 方向三:迁移到 IndexedDB(长期根治)
|
||
|
||
`localStorage` 的 5MB 上限对于包含大量 Base64 图片的医疗报告系统来说迟早会不够用。长期方案是:
|
||
- 引入 `localforage` 或 `idb-keyval` 等轻量库;
|
||
- 将 `storage.ts` 改造为基于 IndexedDB 的异步存储方案(容量可达数百 MB)。
|
||
|
||
**注意:** 方向三涉及全项目的 `storage.get/set` 调用点从同步改为异步,改动面较大,适合作为后续迭代项目。
|
||
|
||
## 建议的实施方案
|
||
|
||
**本次优先执行方向一 + 方向二**,以最快速度解决关键帧丢失问题,并让用户感知到存储异常:
|
||
|
||
1. 在 `captureFrame` 和 `autoCaptureFrames` 中增加 Canvas 等比缩放逻辑:
|
||
```tsx
|
||
const MAX_WIDTH = 800;
|
||
const scale = Math.min(1, MAX_WIDTH / video.videoWidth);
|
||
canvas.width = video.videoWidth * scale;
|
||
canvas.height = video.videoHeight * scale;
|
||
ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||
const dataUrl = canvas.toDataURL('image/jpeg', 0.6);
|
||
```
|
||
|
||
2. 在 `storage.ts` 中增加超限日志:
|
||
```tsx
|
||
} catch (e) {
|
||
console.error('Storage save failed (possibly quota exceeded):', e);
|
||
}
|
||
```
|
||
|
||
## 风险点
|
||
|
||
| 风险 | 级别 | 应对措施 |
|
||
|------|------|---------|
|
||
| 压缩后图片清晰度下降 | 低 | 800px 宽度 + 0.6 质量对于报告插入足够清晰 |
|
||
| 仍有个别超长视频压缩后接近 5MB | 极低 | 配合方向二的日志提示,便于后续继续优化 |
|
||
|
||
## 回滚策略
|
||
|
||
仅调整 Canvas 缩放参数和 JPEG 质量,不涉及数据结构和接口变更。如有异常可直接 revert。
|
||
|
||
---
|
||
|
||
**⚠️ 请审核以上方案,确认无误后回复「确认」或提出修改意见,我将进入测试方案编写阶段。**
|