# 前端系统兼容性审查报告

> 审查范围：`tauri-app/src` 全部源码 + `tauri-app/src-tauri/src` Rust 层命令
> 审查维度：跨平台（macOS/Windows）、Tauri API、媒体/音频、CSS、网络、文件系统
> 审查日期：2026-05-21

---

## 一、综述

本次审查共发现 **28 项兼容性问题**，其中：

| 级别 | 数量 | 说明 |
|------|------|------|
| 🔴 严重 | 6 | 可能导致功能失效、安全漏洞或数据损坏 |
| 🟡 中等 | 11 | 潜在风险，特定场景下会触发问题 |
| 🟢 低风险 | 11 | 建议优化，影响较小或仅存在于边缘场景 |

**关键结论**：
1. **Windows 路径处理是最大隐患**：多处 Rust 代码对 Windows 路径的反斜杠、大小写、UNC 前缀处理不完善，可能导致 FFmpeg 调用失败或安全检查被绕过。
2. **前端内存泄漏已确认 1 处**：`CoverDesign.tsx` 的 `URL.createObjectURL` 未释放。
3. **Asset Protocol 过度授权**：`tauri.conf.json` 中 `"scope": "/**"` 允许 WebView 读取整个文件系统。
4. **CSS/Web API 兼容性良好**：项目运行在 Tauri 封装的 WebView（Edge/WebKit）中，现代 CSS 特性和 Web API 支持度较高，未发现严重兼容性问题。

---

## 二、🔴 严重问题（6 项）

### 1. `URL.createObjectURL` 内存泄漏 — 背景图上传

**位置**：`tauri-app/src/pages/VideoCreation/CoverDesign.tsx:181`

```typescript
const url = URL.createObjectURL(file);
setConfig(prev => ({ ...prev, backgroundImage: url }));
```

**问题**：本地上传背景图时创建 Blob URL，但**从未调用 `URL.revokeObjectURL(url)`**。用户多次上传不同背景图时，旧的 Blob URL 会一直占用内存，直到页面刷新。

**影响**：内存泄漏，长时间使用后可能导致应用卡顿或崩溃。

**修复建议**：
```typescript
const prevUrl = config.backgroundImage;
const url = URL.createObjectURL(file);
setConfig(prev => ({ ...prev, backgroundImage: url }));
// 释放旧的 Blob URL
if (prevUrl?.startsWith('blob:')) {
  URL.revokeObjectURL(prevUrl);
}
// 组件卸载时也要清理
useEffect(() => {
  return () => {
    if (config.backgroundImage?.startsWith('blob:')) {
      URL.revokeObjectURL(config.backgroundImage);
    }
  };
}, []);
```

---

### 2. Windows 敏感路径检查大小写不敏感问题

**位置**：`tauri-app/src-tauri/src/commands/file.rs:47`

```rust
let windows_denied = vec![
    r"c:\windows\",
    r"c:\program files\",
    r"c:\program files (x86)\",
    r"c:\users\all users\",
];
```

**问题**：Windows 文件系统（NTFS）是**大小写保留但大小写不敏感**的。用户传入 `C:\Windows\` 或 `C:\WINDOWS\` 会**完全绕过**上述安全检查。

**影响**：攻击者可通过大小写变体访问系统敏感目录。

**修复建议**：
```rust
let path_lower = path.to_lowercase();
let windows_denied = vec![
    r"c:\windows\",
    r"c:\program files\",
    r"c:\program files (x86)\",
    r"c:\users\all users\",
];
for denied in &windows_denied {
    if path_lower.starts_with(denied) {
        return Err(...);
    }
}
```

---

### 3. Asset Protocol 范围过度授权

**位置**：`tauri-app/src-tauri/tauri.conf.json`

```json
"assetProtocol": {
  "enable": true,
  "scope": ["$APPLOCALDATA/**", "$APPDATA/**", "$APPCONFIG/**", "/**"]
}
```

**问题**：`/**` 允许 WebView 通过 `asset://` 协议读取**整个文件系统的任何文件**。这意味着前端 JavaScript 可以构造 URL 访问用户的任何本地文件（如 `asset:///etc/passwd` 或 `asset://C:/Users/xxx/Documents/`）。

**影响**：严重安全漏洞。即使需要配合路径遍历，也极大扩大了攻击面。

**修复建议**：移除 `/**`，仅保留应用数据目录：
```json
"assetProtocol": {
  "enable": true,
  "scope": ["$APPLOCALDATA/**", "$APPDATA/**", "$APPCONFIG/**"]
}
```

> 注：如果确有需要访问用户选择的文件，应通过 Tauri Dialog API 让用户主动选择，而非开放全局文件系统。

---

### 4. `escape_ffmpeg_path` 不支持 Windows 路径格式

**位置**：`tauri-app/src-tauri/src/ffmpeg_cmd.rs:23`

```rust
fn escape_ffmpeg_path(path: &str) -> String {
    path.replace("'", "'\\''")
}
```

**问题**：该函数仅转义单引号，但**不处理 Windows 反斜杠 `\` 和盘符冒号 `:`**。在 FFmpeg 的 `ass='{}':fontsdir='{}'` filter 语法或 concat demuxer 的 `file 'path'` 格式中，Windows 路径如 `C:\Users\name\video.mp4` 中的反斜杠可能被 FFmpeg 解析为转义序列。

**影响**：Windows 用户在字幕压制、字体加载、视频合成时，FFmpeg 可能因路径解析错误而失败。

**修复建议**：
```rust
fn escape_ffmpeg_path(path: &str) -> String {
    // 1. 统一使用正斜杠（FFmpeg 支持跨平台路径分隔符）
    let normalized = path.replace('\\', "/");
    // 2. 转义单引号（用于 FFmpeg filter 语法中的引号包裹）
    normalized.replace("'", "'\\''")
}
```

> 注意：Windows 上 `C:/Users/...` 这种正斜杠路径 FFmpeg 完全支持，这是最简单的跨平台方案。

---

### 5. `canonicalize()` 在 Windows 上返回 UNC 路径导致下游问题

**位置**：多处使用 `std::fs::canonicalize`

- `tauri-app/src-tauri/src/commands/product.rs:198,258,345`
- `tauri-app/src-tauri/src/commands/project.rs:124,140`
- `tauri-app/src-tauri/src/ffmpeg_cmd.rs:46,792`

**问题**：在 Windows 上，`std::fs::canonicalize()` 返回 UNC 路径格式 `\\?\C:\Users\...`。这种路径格式：
1. **FFmpeg 某些版本不支持**，可能导致命令执行失败
2. **与 `starts_with` 比较时行为异常**，如果比较路径不是 UNC 格式
3. **序列化到 JSON 传给前端时**，前端可能无法正确理解这种路径

**影响**：Windows 上的文件校验、路径比较、FFmpeg 调用可能全部受影响。

**修复建议**：封装一个跨平台的 `normalize_path` 函数，替代 `canonicalize`：
```rust
use std::path::{Path, PathBuf};

fn normalize_path(path: &Path) -> PathBuf {
    // 使用 dunce::simplified() 消除 UNC 前缀，同时保持路径有效性
    dunce::simplified(path).to_path_buf()
}
```

> 需要添加 `dune` crate 依赖，这是 Rust 社区处理 UNC 路径的标准方案。

---

### 6. `atob()` 解析 JWT 存在 base64url 兼容性问题

**位置**：`tauri-app/src/api/client.ts:116`

```typescript
const payload = JSON.parse(atob(token.split('.')[1]));
```

**问题**：JWT 使用 **base64url** 编码（将 `+` → `-`，`/` → `_`，去掉 padding `=`），而 `atob()` 是标准 **base64** 解码器。如果 JWT payload 中包含 `-`、`_` 或需要 padding 的字符，`atob()` 会抛出 `DOMException`。

**当前影响有限**：因为 `exp` 字段通常是纯数字时间戳，但理论上如果用户 ID 或其他 claim 包含这些字符就会失败。

**修复建议**：
```typescript
function base64UrlDecode(str: string): string {
  // base64url → base64
  let padding = '';
  const padLen = 4 - (str.length % 4);
  if (padLen !== 4) {
    padding = '='.repeat(padLen);
  }
  const base64 = str.replace(/-/g, '+').replace(/_/g, '/') + padding;
  return atob(base64);
}

// 使用
const payload = JSON.parse(base64UrlDecode(token.split('.')[1]));
```

---

## 三、🟡 中等问题（11 项）

### 7. `crossOrigin = 'anonymous'` 跨域图片污染 Canvas

**位置**：
- `tauri-app/src/hooks/useCoverFabric.ts:192,235`

```typescript
image.crossOrigin = 'anonymous';
image.src = imagePath;
```

**问题**：当加载远程 HTTP(S) 图片时，如果服务器未配置 `Access-Control-Allow-Origin` 响应头，Canvas 会被**污染（tainted）**。被污染的 Canvas 调用 `toDataURL()` 会抛出 `SecurityError: The canvas has been tainted by cross-origin data`。

当前代码用 try-catch 静默吞掉了错误，用户会看到空白封面，但不知道原因。

**修复建议**：捕获错误并向用户提示：
```typescript
try {
  // ...加载图片...
} catch (err) {
  console.error('封面图片加载失败:', err);
  toast.error('封面图片加载失败，可能是跨域限制或图片链接失效');
}
```

---

### 8. `requestAnimationFrame` + Video 字幕同步在后台标签页节流

**位置**：`tauri-app/src/hooks/useCanvasSubtitleRenderer.ts:158-168`

```typescript
const onFrame = () => {
  drawFrame();
  if (!video.paused) {
    rafRef.current = requestAnimationFrame(onFrame);
  }
};
```

**问题**：当应用窗口不在前台或标签页在后台时，浏览器会**节流 `requestAnimationFrame`**（通常降到 1fps 或完全暂停）。这会导致 Canvas 字幕与视频画面不同步。

**影响**：用户切出应用再切回时，字幕可能短暂错位。

**修复建议**：使用 `video.requestVideoFrameCallback()`（如果支持）作为更精确的同步机制，或在 `visibilitychange` 事件触发时强制重绘：
```typescript
useEffect(() => {
  const onVisibilityChange = () => {
    if (!document.hidden) drawFrame();
  };
  document.addEventListener('visibilitychange', onVisibilityChange);
  return () => document.removeEventListener('visibilitychange', onVisibilityChange);
}, [drawFrame]);
```

---

### 9. `video` 控制条高度硬编码导致字幕定位偏移

**位置**：`tauri-app/src/hooks/useCanvasSubtitleRenderer.ts:86`

```typescript
const VIDEO_CONTROLS_HEIGHT = 40;
```

**问题**：`<video controls>` 的控制条高度在不同浏览器/OS 上不同（macOS Safari 约 30px，Windows 约 40-50px，全屏模式约 0px）。硬编码 40px 会导致字幕在预览时的垂直位置与压制输出不完全一致。

**修复建议**：在视频元数据加载后动态计算：
```typescript
const video = videoRef.current;
if (video) {
  const rect = video.getBoundingClientRect();
  const videoRect = video.videoWidth / video.videoHeight;
  // 实际视频画面高度 = 容器宽度 / 宽高比
  const actualVideoHeight = rect.width / videoRect;
  const controlsHeight = rect.height - actualVideoHeight;
}
```

---

### 10. `audio.duration` 可能返回 `NaN`/`Infinity` 未处理

**位置**：
- `tauri-app/src/pages/VideoCreation/VoiceSynthesis.tsx:360-367`
- `tauri-app/src/pages/VideoGeneration/hooks/useVideoGeneration.ts:371-378`
- `tauri-app/src/pages/ContentManagement/VoiceMaterialLibrary.tsx:163-178`

```typescript
audio.onloadedmetadata = () => {
  clearTimeout(timeoutId);
  resolve(audio.duration);
};
```

**问题**：如果音频文件损坏、格式不支持或元数据缺失，`audio.duration` 可能返回 `NaN` 或 `Infinity`。直接 resolve 这个值会导致下游计算错误。

**修复建议**：
```typescript
audio.onloadedmetadata = () => {
  clearTimeout(timeoutId);
  if (!isFinite(audio.duration) || audio.duration <= 0) {
    reject(new Error('音频时长无效，文件可能损坏或格式不支持'));
  } else {
    resolve(audio.duration);
  }
};
```

---

### 11. `rename` 在 Windows 上目标已存在时失败

**位置**：
- `tauri-app/src-tauri/src/video_processing.rs:93`
- `tauri-app/src-tauri/src/commands/video_compose.rs:222`

**问题**：`std::fs::rename()` 在 Windows 上**如果目标文件已存在会直接失败**（Unix 是原子替换）。代码虽然有 copy 回退，但逻辑可能留下残留文件。

**影响**：Windows 上如果输出路径已存在（如用户重复合成），操作可能失败或留下临时文件。

**修复建议**：在 `rename` 前先删除目标文件（如果存在）：
```rust
if output_path.exists() {
    std::fs::remove_file(&output_path)?;
}
std::fs::rename(&temp_output, &output_path)?;
```

---

### 12. `to_str().unwrap()` 在非 UTF-8 路径上会 panic

**位置**：`tauri-app/src-tauri/src/commands/video_compose.rs:80`

```rust
concat_videos_copy(&app, list_path.to_str().unwrap(), ...)
```

**问题**：Windows 允许非 UTF-8 编码的文件路径（历史 OEM code page 文件）。`to_str()` 返回 `None`，`unwrap()` 会**直接 panic**。

**影响**：极少数 Windows 用户（使用中文 Windows 95/XP 时代遗留文件系统编码）可能导致应用崩溃。

**修复建议**：使用 `to_string_lossy()` 或 `as_os_str()` 传递路径：
```rust
// 如果需要传给 FFmpeg，使用 to_string_lossy()
let path_str = list_path.to_string_lossy();
```

---

### 13. `get_fonts_dir` 开发模式路径探测在 Windows 上可能失效

**位置**：`tauri-app/src-tauri/src/ffmpeg_cmd.rs:298-328`

```rust
cwd.join("fonts"),
parent.join("src-tauri/fonts"),
grandparent.join("tauri-app/src-tauri/fonts"),
```

**问题**：开发模式下的字体目录探测使用 `/` 路径拼接。虽然 `PathBuf::join` 会处理分隔符，但如果开发时的**当前工作目录**与预期不同（如从 IDE 以不同路径启动），探测会失败。

**影响**：开发环境下 Windows 开发者可能遇到字体加载失败。

**修复建议**：添加环境变量覆盖或更健壮的探测逻辑：
```rust
// 优先从环境变量读取
if let Ok(font_dir) = std::env::var("MEIJIAKA_FONTS_DIR") {
    let p = PathBuf::from(font_dir);
    if p.exists() { return Some(p); }
}
```

---

### 14. `concat` demuxer 列表中的 Windows 反斜杠问题

**位置**：`tauri-app/src-tauri/src/commands/video_compose.rs:65`

```rust
format!("file '{}'\n", ffmpeg_cmd::escape_ffmpeg_path(path))
```

**问题**：FFmpeg concat demuxer 的列表文件格式中，`file 'path'` 语法在 Windows 上如果路径包含反斜杠，反斜杠可能被 FFmpeg 解释为转义字符。

**影响**：与问题 #4 类似，Windows 路径导致 FFmpeg 解析错误。

**修复建议**：在写入 concat 列表前统一将路径中的 `\` 替换为 `/`：
```rust
let normalized = path.replace('\\', "/");
format!("file '{}'\n", normalized)
```

---

### 15. `load_app_config` 失败时无降级配置

**位置**：`tauri-app/src/main.tsx:10-16`

```typescript
try {
  const config = await loadAppConfig();
  appEnvironment = config.environment;
} catch {
  // 加载失败时默认为生产模式
}
```

**问题**：如果 `load_app_config`（Tauri IPC 调用）失败，应用降级为生产模式。这是合理的，但**生产模式会禁用右键菜单和 F12 DevTools**。开发者在调试时如果 IPC 调用失败，会突然失去所有调试能力，且不知道原因。

**修复建议**：在降级时输出警告日志：
```typescript
} catch (e) {
  console.warn('[bootstrap] 加载应用配置失败，降级为生产模式:', e);
  appEnvironment = 'production';
}
```

---

### 16. `window.location.reload()` 在 Tauri 中行为不确定

**位置**：`tauri-app/src/pages/Settings/Settings.tsx:178`

```typescript
setTimeout(() => { window.location.reload(); }, 500);
```

**问题**：Tauri 应用中的 `window.location.reload()` 行为与浏览器不同。在某些 Tauri 版本中可能导致：
- 白屏而非正常刷新
- WebView 进程崩溃
- 状态丢失但窗口不重新加载

**修复建议**：使用 Tauri 的 `relaunch()` 命令重启整个应用，或重新挂载 React 根组件：
```typescript
import { relaunch } from '@tauri-apps/plugin-process';
// 重启应用
await relaunch();
```

---

### 17. `Math.random()` 用于缓存清除参数（安全性）

**位置**：`tauri-app/src/api/client.ts:334`

```typescript
const cacheBuster = `_t=${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
```

**问题**：`Math.random()` 不是加密安全的随机数生成器。虽然这里只是用于缓存清除，但如果未来用于其他安全相关场景会有风险。

**修复建议**：使用 `crypto.randomUUID()` 或 `crypto.getRandomValues()`：
```typescript
const cacheBuster = `_t=${Date.now()}_${crypto.randomUUID().slice(0, 8)}`;
```

---

## 四、🟢 低风险/建议（11 项）

### 18. `backdrop-filter` 无标准前缀回退

**位置**：`tauri-app/src/pages/VideoCreation/CoverDesign.css:444-445`

```css
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
```

**评估**：当前代码同时有标准和 WebKit 前缀版本，在 Tauri WebView（Edge/WebKit）中支持良好。Firefox 不支持，但项目不面向 Firefox。**无需修复**。

---

### 19. `::-webkit-scrollbar` 在 Firefox 中无效

**位置**：多处（`global.css`、`CoverDesign.css` 等）

**评估**：项目运行在 Tauri WebView 中（基于系统浏览器引擎），不是 Firefox。在 Windows 上基于 WebView2（Edge），macOS 上基于 WKWebView（Safari），均支持 WebKit 滚动条样式。**无需修复**。

---

### 20. `aspect-ratio` 在旧版 Safari 中可能不支持

**位置**：多处使用 `aspect-ratio: 9 / 16`

**评估**：macOS 12+ 的 Safari 支持 `aspect-ratio`。如果目标用户可能使用较旧的 macOS 版本，可能需要 `padding-top: 177.77%` 回退。但鉴于这是 Tauri 桌面应用，可以控制最低系统版本。**建议确认 `tauri.conf.json` 中 `macOS.minimumSystemVersion` 是否要求 12.0+**。

---

### 21. `requestIdleCallback` 缺失回退不完整

**位置**：`tauri-app/src/main.tsx:75-79`

```typescript
if ('requestIdleCallback' in window) {
  requestIdleCallback(showWindow, { timeout: 500 });
} else {
  setTimeout(showWindow, 100);
}
```

**评估**：Tauri WebView2（Edge）和 WKWebView（Safari）均支持 `requestIdleCallback`。回退逻辑也已实现。**无需修复**。

---

### 22. `navigator.userAgent` 已被冻结

**位置**：
- `tauri-app/src/store/authStore.ts:254`
- `tauri-app/src/api/client.ts:259`

**评估**：虽然现代浏览器正在限制 `navigator.userAgent`，但 Tauri WebView 不受此限制。且当前用法仅为日志和登录信息上报，不影响功能。**无需修复**。

---

### 23. `document.fonts.check()` 参数格式兼容性

**位置**：`tauri-app/src/utils/canvasSubtitleDrawer.ts:209`

```typescript
if (document.fonts.check(`bold 16px ${fontName}`)) {
```

**评估**：`document.fonts.check()` 的参数格式在不同浏览器中实现有细微差异，但 Tauri WebView2/WKWebView 均支持此用法。**风险极低**。

---

### 24. `Date.now()` 连续调用可能冲突

**位置**：多处使用 `Date.now()` 生成文件名

**评估**：仅在极快速连续调用时（<1ms）可能冲突。当前场景下不太可能。**风险极低**。

---

### 25. `autoPlay` 视频可能被浏览器阻止

**位置**：多处 `<video autoPlay>`

**评估**：桌面应用中的 WebView 通常不受浏览器自动播放策略限制。但如果用户操作系统设置了辅助功能限制，仍可能被阻止。**建议添加 `muted` 属性作为后备**（如果需要自动播放且带声音）。

---

### 26. `file.path` 是非标准 Chromium 属性

**位置**：`tauri-app/src/pages/VideoCreation/CoverDesign.tsx:178`

```typescript
const path = (file as any).path || (file as any).webkitRelativePath || '';
```

**评估**：`File.path` 是 Chromium 的私有属性，在标准浏览器（Firefox）中不存在。但由于项目运行在 Tauri（Chromium/WebView2）中，这**当前是可行的**。但如果未来需要支持 Web 端部署，需要改用 Tauri Dialog API 获取路径。**建议添加注释说明此依赖**。

---

### 27. `storage/engine.rs` 无 Windows 文件权限设置

**位置**：`tauri-app/src-tauri/src/storage/engine.rs:161`

```rust
#[cfg(unix)]
fn set_restrictive_permissions(path: &Path) -> Result<()> {
    use std::os::unix::fs::PermissionsExt;
    let mut perms = std::fs::metadata(path)?.permissions();
    perms.set_mode(0o600);
    std::fs::set_permissions(path, perms)?;
    Ok(())
}
```

**评估**：Unix 上设置了 0o600 权限，Windows 上跳过。Windows 上文件默认对用户可读可写，其他用户也可读（取决于 ACL）。虽然这不是严重安全问题（应用数据存储在用户目录），但**建议在 Windows 上设置等效的 ACL 限制**。

**修复建议**：
```rust
#[cfg(windows)]
fn set_restrictive_permissions(path: &Path) -> Result<()> {
    // 使用 windows  crate 或 fs_extra 设置 ACL
    // 简化为仅当前用户可读写
    // 这是可选优化，优先级低
    Ok(())
}
```

---

### 28. `Slider.css` 和 `CoverDesign.css` 中 `appearance` 重复声明

**位置**：
- `tauri-app/src/components/Slider/Slider.css:32-33`
- `tauri-app/src/pages/VideoCreation/CoverDesign.css:79-80`

```css
appearance: none;
appearance: none;
```

**评估**：纯代码质量问题，不影响兼容性。**建议移除重复行**。

---

## 五、按维度汇总表

| 维度 | 严重 | 中等 | 低风险 | 主要文件 |
|------|------|------|--------|----------|
| **内存/资源管理** | 1 (#1) | 1 (#10) | 1 (#24) | CoverDesign.tsx, VoiceSynthesis.tsx |
| **Windows 路径** | 2 (#4, #5) | 3 (#11, #12, #14) | 1 (#27) | ffmpeg_cmd.rs, file.rs, product.rs |
| **安全检查** | 2 (#2, #3) | 0 | 0 | file.rs, tauri.conf.json |
| **Canvas/媒体** | 0 | 3 (#7, #8, #9) | 2 (#20, #25) | useCoverFabric.ts, useCanvasSubtitleRenderer.ts |
| **网络/API** | 1 (#6) | 1 (#17) | 2 (#22, #23) | client.ts |
| **Tauri 原生** | 0 | 1 (#16) | 1 (#26) | Settings.tsx, CoverDesign.tsx |
| **CSS** | 0 | 0 | 3 (#18, #19, #28) | CoverDesign.css, global.css |
| **启动/配置** | 0 | 1 (#15) | 1 (#21) | main.tsx |
| **字体加载** | 0 | 1 (#13) | 0 | ffmpeg_cmd.rs |

---

## 六、修复优先级建议

### 立即修复（影响功能/安全）

1. **#3 Asset Protocol 过度授权** — 安全漏洞，一行配置修改
2. **#2 Windows 敏感路径大小写** — 安全检查被绕过
3. **#1 URL.createObjectURL 泄漏** — 内存泄漏，用户可见
4. **#4 escape_ffmpeg_path Windows 支持** — Windows 功能失效
5. **#5 canonicalize() UNC 路径** — Windows 文件操作异常

### 本轮迭代修复（影响体验）

6. **#6 atob() base64url 兼容性** — Token 解析潜在失败
7. **#7 crossOrigin 图片污染提示** — 用户友好性
8. **#8 RAF 后台节流** — 字幕同步
9. **#10 audio.duration NaN 处理** — 音频处理健壮性
10. **#9 控制条高度硬编码** — 预览准确性
11. **#11 Windows rename 已存在** — 文件操作健壮性

### 后续排期（优化/边缘场景）

12-28. 其余低风险项

---

## 七、特别说明：Tauri 环境 vs 浏览器环境的兼容性差异

本项目同时支持两种运行模式：

| 特性 | Tauri 桌面模式 | 浏览器模式（开发调试用） |
|------|---------------|------------------------|
| `invoke()` | ✅ Tauri IPC | ❌ 会 catch 失败 |
| `convertFileSrc()` | ✅ `asset://` | ❌ 会 catch 失败 |
| `localStorage` | ✅ 可用 | ✅ 可用 |
| File 系统 API | ✅ Tauri 插件 | ❌ 不可用 |
| `__TAURI_INTERNALS__` | ✅ 存在 | ❌ 不存在 |

**当前代码对浏览器模式有降级处理**（`isTauri()` 检查 + catch 错误），这是好的实践。但以下功能在浏览器模式下完全不可用，需要评估是否影响开发调试：

- 本地视频预览（依赖 `asset://` + FFmpeg 转码）
- 文件保存/导出（依赖 Tauri Dialog）
- 项目本地持久化（依赖 Tauri IPC）
- 自动更新（依赖 Tauri Updater）

**建议**：在 `README` 或开发文档中明确列出浏览器模式的功能限制，避免开发者困惑。
