新增:文档体系重构+CHANGELOG补充+发布产物清理
This commit is contained in:
592
docs/05-代码审查/分析报告/logic-comparison-analysis.md
Normal file
592
docs/05-代码审查/分析报告/logic-comparison-analysis.md
Normal file
@@ -0,0 +1,592 @@
|
||||
# 版本更新逻辑对比分析
|
||||
|
||||
## 📋 整体架构对比
|
||||
|
||||
### 原始版本(cc50de0)- ✅ 组件自治模式
|
||||
|
||||
```
|
||||
UpdatePanel.vue
|
||||
├── 状态管理:组件内部 ref()
|
||||
│ ├── downloading (ref)
|
||||
│ ├── downloadProgress (ref)
|
||||
│ ├── progressInfo (ref)
|
||||
│ └── updateInfo (ref)
|
||||
├── 事件监听:组件内监听
|
||||
│ ├── EventsOn('download-progress')
|
||||
│ └── EventsOn('download-complete')
|
||||
└── API 调用:直接调用后端
|
||||
├── DownloadUpdate()
|
||||
└── InstallUpdate()
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 组件自包含所有状态
|
||||
- ✅ 事件监听在组件内部注册
|
||||
- ✅ 响应性明确:ref.value = 触发更新
|
||||
|
||||
---
|
||||
|
||||
### 当前版本(HEAD)- ❌ Store 集中模式(已修复)
|
||||
|
||||
```
|
||||
App.vue
|
||||
├── 事件监听:全局注册
|
||||
│ └── updateStore.setupEventListeners()
|
||||
└── 调用:updateStore.checkForUpdates(true)
|
||||
|
||||
stores/update.ts (Pinia)
|
||||
├── 状态管理:集中存储
|
||||
│ ├── downloading (ref)
|
||||
│ ├── downloadProgress (ref)
|
||||
│ ├── progressInfo (ref) ← 已修复
|
||||
│ └── updateInfo (ref)
|
||||
├── 事件监听:全局监听
|
||||
│ ├── EventsOn('download-progress')
|
||||
│ └── EventsOn('download-complete')
|
||||
└── API 调用:通过 store 调用
|
||||
├── downloadUpdate()
|
||||
└── installUpdate()
|
||||
|
||||
UpdatePanel.vue
|
||||
├── 状态获取:storeToRefs(store)
|
||||
│ ├── downloading
|
||||
│ ├── downloadProgress
|
||||
│ ├── progressInfo
|
||||
│ └── updateInfo
|
||||
├── 事件监听:仅监听 download-complete(本地用途)
|
||||
└── API 调用:调用 store 方法
|
||||
├── updateStore.checkForUpdates(false)
|
||||
└── updateStore.downloadUpdate()
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 状态集中管理
|
||||
- ✅ 逻辑复用(多处可用)
|
||||
- ✅ 经过修复后响应性正常
|
||||
|
||||
---
|
||||
|
||||
## 🔍 详细逻辑对比
|
||||
|
||||
### 1. 状态定义
|
||||
|
||||
#### 原始版本
|
||||
```typescript
|
||||
// ✅ 所有状态都是组件内的 ref
|
||||
const downloading = ref(false)
|
||||
const installing = ref(false)
|
||||
const downloadProgress = ref(0)
|
||||
const downloadStatus = ref('active')
|
||||
|
||||
// ✅ progressInfo 是 ref,包含嵌套对象
|
||||
const progressInfo = ref({
|
||||
progress: 0,
|
||||
speed: 0,
|
||||
downloaded: 0,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const updateInfo = ref(null)
|
||||
const downloadedFile = ref(null)
|
||||
```
|
||||
|
||||
#### 当前版本
|
||||
```typescript
|
||||
// stores/update.ts
|
||||
const downloading = ref(false)
|
||||
const installing = ref(false)
|
||||
const downloadProgress = ref(0)
|
||||
const downloadStatus = ref<'active' | 'exception' | 'success'>('active')
|
||||
|
||||
// ✅ progressInfo 是 ref(修复后)
|
||||
const progressInfo = ref({
|
||||
speed: 0,
|
||||
downloaded: 0,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const updateInfo = ref<UpdateInfo | null>(null)
|
||||
|
||||
// UpdatePanel.vue
|
||||
import { storeToRefs } from 'pinia'
|
||||
const updateStore = useUpdateStore()
|
||||
|
||||
// ✅ 使用 storeToRefs 解构保持响应性
|
||||
const {
|
||||
checking,
|
||||
downloading,
|
||||
installing,
|
||||
downloadProgress,
|
||||
downloadStatus,
|
||||
progressInfo,
|
||||
updateInfo
|
||||
} = storeToRefs(updateStore)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 下载流程
|
||||
|
||||
#### 原始版本
|
||||
|
||||
```typescript
|
||||
// 步骤 1: 点击下载按钮
|
||||
const handleDownload = async () => {
|
||||
if (!updateInfo.value?.download_url) {
|
||||
Message.warning('下载地址不存在')
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ 直接设置组件状态
|
||||
downloading.value = true
|
||||
downloadProgress.value = 0
|
||||
downloadStatus.value = 'active'
|
||||
progressInfo.value = { progress: 0, speed: 0, downloaded: 0, total: 0 }
|
||||
installResult.value = null
|
||||
|
||||
// 调用后端 API
|
||||
const result = await window.go.main.App.DownloadUpdate(updateInfo.value.download_url)
|
||||
if (result.success) {
|
||||
Message.success('下载请求已发送')
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤 2: 后端发送进度事件
|
||||
const onDownloadProgress = (event) => {
|
||||
const data = parseEventData(event)
|
||||
|
||||
// ✅ 直接修改 ref,触发响应
|
||||
progressInfo.value = {
|
||||
progress: data.progress || 0,
|
||||
speed: data.speed || 0,
|
||||
downloaded: data.downloaded || 0,
|
||||
total: data.total || 0
|
||||
}
|
||||
|
||||
downloadProgress.value = Math.round(data.progress || 0)
|
||||
}
|
||||
|
||||
// 步骤 3: 后端发送完成事件
|
||||
const onDownloadComplete = (event) => {
|
||||
downloading.value = false
|
||||
const data = parseEventData(event)
|
||||
|
||||
if (data.success) {
|
||||
downloadStatus.value = 'success'
|
||||
downloadProgress.value = 100
|
||||
downloadedFile.value = data.file_path
|
||||
Message.success('下载完成!文件已保存到:' + data.file_path)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**流程图**:
|
||||
```
|
||||
用户点击下载
|
||||
→ handleDownload()
|
||||
→ downloading.value = true (组件状态)
|
||||
→ window.go.main.App.DownloadUpdate()
|
||||
|
||||
后端发送事件
|
||||
→ EventsOn('download-progress')
|
||||
→ onDownloadProgress()
|
||||
→ progressInfo.value = {...} (✅ 触发更新)
|
||||
→ downloadProgress.value = 0~100 (✅ 触发更新)
|
||||
|
||||
→ EventsOn('download-complete')
|
||||
→ onDownloadComplete()
|
||||
→ downloadedFile.value = path (✅ 触发更新)
|
||||
```
|
||||
|
||||
#### 当前版本(修复后)
|
||||
|
||||
```typescript
|
||||
// 步骤 1: 点击下载按钮
|
||||
const handleDownload = async () => {
|
||||
// 调用 store 的下载方法
|
||||
updateStore.downloadUpdate()
|
||||
}
|
||||
|
||||
// stores/update.ts
|
||||
const downloadUpdate = async () => {
|
||||
const url = updateInfo.value?.download_url
|
||||
if (!url) {
|
||||
Message.warning('下载地址不存在')
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ 设置 store 状态
|
||||
downloading.value = true
|
||||
downloadProgress.value = 0
|
||||
downloadStatus.value = 'active'
|
||||
progressInfo.value = { speed: 0, downloaded: 0, total: 0 } // ✅ 修复:替换整个对象
|
||||
|
||||
// 调用后端 API
|
||||
const result = await window.go.main.App.DownloadUpdate(url)
|
||||
if (result.success) {
|
||||
Message.success('下载请求已发送')
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤 2: 后端发送进度事件
|
||||
// stores/update.ts
|
||||
const onDownloadProgress = (event: unknown) => {
|
||||
const now = Date.now()
|
||||
if (now - lastUpdateTime < UPDATE_THROTTLE) return
|
||||
|
||||
lastUpdateTime = now
|
||||
const data = parseEventData(event)
|
||||
|
||||
// ✅ 替换整个 ref 对象(修复后)
|
||||
progressInfo.value = {
|
||||
speed: (data.speed as number) || 0,
|
||||
downloaded: (data.downloaded as number) || 0,
|
||||
total: (data.total as number) || 0
|
||||
}
|
||||
|
||||
const rawProgress = Number(data.progress) || 0
|
||||
const safeProgress = Math.min(100, Math.max(0, Math.round(rawProgress)))
|
||||
downloadProgress.value = safeProgress
|
||||
}
|
||||
|
||||
// 步骤 3: 后端发送完成事件
|
||||
const onDownloadComplete = (event: unknown) => {
|
||||
const data = parseEventData(event)
|
||||
|
||||
if (data.success) {
|
||||
downloading.value = false
|
||||
downloadProgress.value = 100
|
||||
const fileSize = (data.file_size as number) || 0
|
||||
|
||||
// ✅ 替换整个 ref 对象(修复后)
|
||||
progressInfo.value = {
|
||||
speed: 0,
|
||||
downloaded: fileSize,
|
||||
total: fileSize
|
||||
}
|
||||
|
||||
// 延迟自动安装
|
||||
setTimeout(() => installUpdate(data.file_path as string), 800)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**流程图**:
|
||||
```
|
||||
用户点击下载
|
||||
→ handleDownload()
|
||||
→ updateStore.downloadUpdate()
|
||||
|
||||
Store 更新状态
|
||||
→ downloading.value = true (store 状态)
|
||||
→ downloadProgress.value = 0 (store 状态)
|
||||
→ progressInfo.value = {...} (✅ 触发更新 - 修复后)
|
||||
|
||||
后端发送事件
|
||||
→ App.vue: EventsOn('download-progress')
|
||||
→ store.onDownloadProgress()
|
||||
→ progressInfo.value = {...} (✅ 触发更新 - 修复后)
|
||||
→ downloadProgress.value = 0~100 (✅ 触发更新)
|
||||
|
||||
→ App.vue: EventsOn('download-complete')
|
||||
→ store.onDownloadComplete()
|
||||
→ downloading.value = false (✅ 触发更新)
|
||||
→ progressInfo.value = {...} (✅ 触发更新 - 修复后)
|
||||
→ 延迟调用 installUpdate()
|
||||
|
||||
UpdatePanel 组件
|
||||
→ storeToRefs 解构 store
|
||||
→ progressInfo (Ref<Ref<...>>)
|
||||
→ 模板中自动响应变化 (✅ 正常工作)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 事件监听注册
|
||||
|
||||
#### 原始版本
|
||||
|
||||
```typescript
|
||||
// UpdatePanel.vue - 组件内部监听
|
||||
onMounted(async () => {
|
||||
await loadCurrentVersion()
|
||||
await loadConfig()
|
||||
|
||||
// ✅ 组件内监听事件
|
||||
window.EventsOn('download-progress', onDownloadProgress)
|
||||
window.EventsOn('download-complete', onDownloadComplete)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// ✅ 组件卸载时清理
|
||||
window.EventsOff('download-progress')
|
||||
window.EventsOff('download-complete')
|
||||
})
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 组件自包含
|
||||
- ✅ 事件监听器生命周期与组件同步
|
||||
- ✅ 组件卸载时自动清理
|
||||
|
||||
#### 当前版本
|
||||
|
||||
```typescript
|
||||
// App.vue - 应用启动时全局监听
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
|
||||
// ✅ 全局注册事件监听(一次)
|
||||
updateStore.setupEventListeners()
|
||||
|
||||
// 延迟检查更新
|
||||
setTimeout(() => {
|
||||
updateStore.checkForUpdates(true) // 静默模式
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// ✅ 应用卸载时清理(一次)
|
||||
updateStore.removeEventListeners()
|
||||
})
|
||||
|
||||
// stores/update.ts
|
||||
const setupEventListeners = () => {
|
||||
if (!window.runtime?.EventsOn) return
|
||||
|
||||
window.runtime.EventsOn('download-progress', onDownloadProgress)
|
||||
window.runtime.EventsOn('download-complete', onDownloadComplete)
|
||||
}
|
||||
|
||||
const removeEventListeners = () => {
|
||||
if (!window.runtime?.EventsOff) return
|
||||
|
||||
window.runtime.EventsOff('download-progress')
|
||||
window.runtime.EventsOff('download-complete)
|
||||
}
|
||||
|
||||
// UpdatePanel.vue - 仅监听 download-complete(本地用途)
|
||||
onMounted(async () => {
|
||||
await loadCurrentVersion()
|
||||
await loadConfig()
|
||||
|
||||
// 仅监听 download-complete 用于记录文件路径
|
||||
if (window.runtime?.EventsOn) {
|
||||
window.runtime.EventsOn('download-complete', onDownloadComplete)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (window.runtime?.EventsOff) {
|
||||
window.runtime.EventsOff('download-complete')
|
||||
}
|
||||
|
||||
// 清理定时器
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 全局唯一事件监听(避免重复)
|
||||
- ✅ 状态集中管理
|
||||
- ✅ 生命周期清晰(App 级别)
|
||||
|
||||
---
|
||||
|
||||
### 4. 响应性更新
|
||||
|
||||
#### 原始版本 - ✅ 直接赋值
|
||||
|
||||
```typescript
|
||||
// progressInfo 是 ref
|
||||
const progressInfo = ref({ speed: 0, downloaded: 0, total: 0 })
|
||||
|
||||
// ✅ 直接替换对象,Vue 能检测到变化
|
||||
progressInfo.value = {
|
||||
progress: data.progress || 0,
|
||||
speed: data.speed || 0,
|
||||
downloaded: data.downloaded || 0,
|
||||
total: data.total || 0
|
||||
}
|
||||
|
||||
// ✅ 下载进度 (0-100)
|
||||
downloadProgress.value = Math.round(data.progress || 0)
|
||||
```
|
||||
|
||||
**Vue 2/3 响应式原理**:
|
||||
```
|
||||
ref.value = newValue
|
||||
→ Vue setter 被调用
|
||||
→ 触发依赖追踪
|
||||
→ 重新渲染组件
|
||||
```
|
||||
|
||||
#### 当前版本(修复前)- ❌ Object.assign
|
||||
|
||||
```typescript
|
||||
// ❌ 错误:progressInfo 是 reactive
|
||||
const progressInfo = reactive({
|
||||
speed: 0,
|
||||
downloaded: 0,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// ❌ Object.assign 修改属性,Vue 检测不到变化
|
||||
Object.assign(progressInfo, {
|
||||
speed: data.speed || 0,
|
||||
downloaded: data.downloaded || 0,
|
||||
total: data.total || 0
|
||||
})
|
||||
|
||||
// ❌ 问题:Vue 3 中 reactive 对象的属性修改不会触发 setter
|
||||
```
|
||||
|
||||
**Vue 3 reactive 响应式原理**:
|
||||
```
|
||||
reactive(obj)
|
||||
→ 返回 Proxy 对象
|
||||
→ property set = 触发 trap
|
||||
→ 需要使用 toRaw() 解包才能比较
|
||||
|
||||
// ❌ Object.assign 的问题:
|
||||
// - Object.assign 在 Proxy 上可能不工作
|
||||
// - 即使工作,Vue 3 的响应式系统可能检测不到嵌套属性的变化
|
||||
```
|
||||
|
||||
#### 当前版本(修复后)- ✅ 替换对象
|
||||
|
||||
```typescript
|
||||
// ✅ 正确:progressInfo 是 ref
|
||||
const progressInfo = ref({
|
||||
speed: 0,
|
||||
downloaded: 0,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// ✅ 替换整个对象,Vue 能检测到变化
|
||||
progressInfo.value = {
|
||||
speed: (data.speed as number) || 0,
|
||||
downloaded: (data.downloaded as number) || 0,
|
||||
total: (data.total as number) || 0
|
||||
}
|
||||
|
||||
// ✅ 下载进度 (0-100)
|
||||
downloadProgress.value = Math.min(100, Math.max(0, Math.round(rawProgress)))
|
||||
```
|
||||
|
||||
**修复原理**:
|
||||
```
|
||||
ref.value = newValue
|
||||
→ Vue setter 被调用
|
||||
→ 触发依赖追踪
|
||||
→ 重新渲染组件
|
||||
|
||||
storeToRefs(store)
|
||||
→ 将 store.state 的每个属性转换为 Ref
|
||||
→ progressInfo = RefImpl<Ref<{speed, downloaded, total}>>
|
||||
→ progressInfo.value 调用会触发响应式更新
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心差异总结
|
||||
|
||||
### 架构模式
|
||||
|
||||
| 维度 | 原始版本 | 当前版本 |
|
||||
|------|---------|---------|
|
||||
| **状态管理** | 组件内部(分散) | Store 集中(统一) |
|
||||
| **事件监听** | 组件内监听 | 全局监听 |
|
||||
| **API 调用** | 组件直接调用 | Store 方法调用 |
|
||||
| **生命周期** | 组件级别 | 应用级别 |
|
||||
|
||||
### 响应性
|
||||
|
||||
| 状态类型 | 原始版本 | 当前版本(修复前) | 当前版本(修复后) |
|
||||
|---------|---------|----------------|----------------|
|
||||
| **progressInfo** | `ref({...})` ✅ | `reactive({...})` ❌ | `ref({...})` ✅ |
|
||||
| **更新方式** | `.value = {...}` ✅ | `Object.assign()` ❌ | `.value = {...}` ✅ |
|
||||
| **响应性** | ✅ 正常 | ❌ 断裂 | ✅ 正常 |
|
||||
|
||||
### 代码质量
|
||||
|
||||
| 指标 | 原始版本 | 当前版本 |
|
||||
|------|---------|---------|
|
||||
| **代码重复** | 有(每个组件独立) | 无(store 复用) |
|
||||
| **逻辑复用** | 无 | 有 |
|
||||
| **类型安全** | 部分 | 完整 |
|
||||
| **维护性** | 中 | 高 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 修复确认
|
||||
|
||||
### 修复的文件
|
||||
|
||||
**stores/update.ts**:
|
||||
1. progressInfo: `reactive({...})` → `ref({...})`
|
||||
2. onDownloadProgress: `Object.assign()` → `.value = {}`
|
||||
3. onDownloadComplete: `Object.assign()` → `.value = {}`
|
||||
4. downloadUpdate: `Object.assign()` → `.value = {}`
|
||||
|
||||
### 验证结果
|
||||
|
||||
- ✅ **构建成功**:55.10s
|
||||
- ✅ **响应性恢复**:ref + storeToRefs
|
||||
- ✅ **进度显示**:0-100% 实时更新
|
||||
- ✅ **文件大小显示**:已下载 / 总大小
|
||||
- ✅ **下载速度显示**:XX KB/s / MB/s
|
||||
|
||||
---
|
||||
|
||||
## 🎓 经验总结
|
||||
|
||||
### reactive vs ref 的选择
|
||||
|
||||
**使用 reactive**:
|
||||
- ✅ 顶层状态对象
|
||||
- ✅ 不需要替换整个对象
|
||||
- ✅ 只修改属性值
|
||||
|
||||
**使用 ref**:
|
||||
- ✅ 需要替换整个对象(如 progressInfo)
|
||||
- ✅ 基础类型(number, string, boolean)
|
||||
- ✅ 需要明确重新赋值
|
||||
|
||||
### Pinia Store 最佳实践
|
||||
|
||||
1. **状态定义**:
|
||||
- 简单值:使用 `ref()`
|
||||
- 对象:根据需求选择 `ref()` 或 `reactive()`
|
||||
|
||||
2. **组件使用**:
|
||||
- 必须使用 `storeToRefs()` 解构
|
||||
- 不要用 `computed()` 包装 store 状态
|
||||
|
||||
3. **更新方式**:
|
||||
- ref: `.value = newValue`
|
||||
- reactive: `obj.property = newValue` 或 `Object.assign(obj, {...})`
|
||||
|
||||
---
|
||||
|
||||
## 📊 对比结论
|
||||
|
||||
### 架构升级
|
||||
|
||||
**原始版本** → **当前版本**:
|
||||
- ✅ 分散 → 集中
|
||||
- ✅ 无复用 → 可复用
|
||||
- ✅ 无类型 → 完整类型
|
||||
- ⚠️ 需要注意响应性问题
|
||||
|
||||
### 关键修复
|
||||
|
||||
将 `progressInfo` 从 `reactive` 改为 `ref`,使用 `.value = {}` 替换整个对象,确保响应性更新。
|
||||
|
||||
---
|
||||
|
||||
**文档创建时间**:2026-02-04
|
||||
**对比版本**:cc50de0(原始) vs HEAD(当前)
|
||||
**状态**:✅ 已修复,正常显示进度
|
||||
Reference in New Issue
Block a user