8.1 KiB
更新中心图标逐级回退设计
背景
当前更新中心的图标解析分成两段:
- 主进程
electron/main/backend/update-center/icons.ts会优先解析本地图标,解析不到时再直接返回线上图标 URL。 - 渲染层
src/components/update-center/UpdateCenterItem.vue只接收单个icon字段,图片加载失败后直接回退到默认占位图。
这个结构已经满足“本地优先”的静态选择,但不能满足新的行为要求:
- 当本地图标成功加载时,不再请求线上图标和默认图标。
- 当本地图标路径虽然存在、但实际加载失败时,继续尝试线上图标。
- 当线上图标也失败时,最后才回退到默认占位图。
问题根因不是路径优先级判断错误,而是当前前后端只传递了一个最终 icon 值,导致前端无法在运行时根据真实加载结果继续尝试下一层来源。
目标
- 更新中心图标加载顺序固定为:
localIcon -> remoteIcon -> placeholder。 - 本地图标加载成功时,不再加载线上图标和默认图标。
- 本地图标加载失败时,自动切换到线上图标。
- 线上图标也失败时,才显示默认占位图。
- 保持当前更新中心列表布局、图标尺寸和已有解析路径规则不变。
非目标
- 不改动主商店、已安装列表或其他页面的图标逻辑。
- 不增加新的网络探测请求,也不预检远程图标是否可访问。
- 不重构现有本地图标解析算法,只调整数据结构和回退链路。
- 不引入通用的图标来源数组或复杂图标对象。
方案选择
本次考虑过三种方案:
- 后端透传
localIcon和remoteIcon两个字段,前端顺序尝试。 - 后端透传
iconCandidates: string[],前端按数组顺序尝试。 - 继续只传一个
icon,前端根据pkgname/category/arch自己重新拼线上图标地址。
最终选择方案 1。
原因:
- 它刚好对应本次明确的三级回退需求,最小且直接。
- 后端继续掌握图标来源规则,避免前端复制商店 URL 规则。
- 相比数组方案,双字段更易读、更容易在 IPC 类型中维护。
- 前端只负责“加载失败后切换到下一来源”,职责边界清晰。
设计概览
更新中心改为“主进程解析来源,渲染层控制加载顺序”的结构:
- 主进程为每个更新项分别计算
localIcon和remoteIcon。 - 服务层和前端类型透传这两个字段。
UpdateCenterItem.vue按localIcon -> remoteIcon -> placeholder的顺序逐级尝试。- 候选图标一旦成功加载,组件不再切换到后续来源。
数据结构变更
主进程类型
修改:electron/main/backend/update-center/types.ts
将:
icon?: string;
改为:
localIcon?: string;
remoteIcon?: string;
Service Snapshot
修改:electron/main/backend/update-center/service.ts
更新 renderer-facing item/task 类型,并在 toState() 中透传:
localIcon?: string;
remoteIcon?: string;
渲染层类型
修改:src/global/typedefinition.ts
更新 UpdateCenterItem 和 UpdateCenterTaskState:
localIcon?: string;
remoteIcon?: string;
模块边界
electron/main/backend/update-center/icons.ts
保留现有职责,但返回内容从“单个最终图标”调整为“两种候选来源”:
resolveDesktopIcon(pkgname):解析传统 deb / aptss 更新项的本地图标。resolveApmIcon(pkgname):解析 APM 更新项的本地图标。buildRemoteFallbackIconUrl(item):拼接远程商店图标地址。resolveUpdateItemIcons(item):组合出{ localIcon?, remoteIcon? }。
这里不再提前做“本地失败就直接放弃线上”的最终决策,而是把两个候选来源都准备好交给前端。
electron/main/backend/update-center/index.ts
在更新项 enrichment 阶段,将:
- 现有的单
icon注入逻辑。
调整为:
- 读取
resolveUpdateItemIcons(item)的结果。 - 仅在字段存在时把
localIcon/remoteIcon写回更新项。
src/components/update-center/UpdateCenterItem.vue
组件不再把单个 item.icon 当成最终地址,而是:
- 从
item.localIcon和item.remoteIcon派生候选列表。 - 使用当前索引决定
img.src。 - 失败时切到下一候选项。
- 候选项耗尽后切到占位图。
详细数据流
主进程加载更新项
- 更新中心主进程加载更新项。
- 现有逻辑继续补齐
category、arch等字段。 - 图标模块为每个项分别解析:
localIcon:本地图标路径。remoteIcon:线上图标 URL。
- enrichment 后的更新项通过 service snapshot 发送到渲染层。
渲染层展示更新项
- 组件收到
item.localIcon/item.remoteIcon。 - 组件构造一个有序候选列表:
- 本地路径转换为
file://URL。 - 远程 URL 原样使用。
- 本地路径转换为
- 初始渲染第 1 个候选图标。
- 如果
img加载成功,流程结束,不再切换到下一项。 - 如果
img触发error,索引递增,继续尝试下一候选图标。 - 如果所有候选都失败,切换到占位图。
前端行为细节
候选列表生成规则
候选列表只包含存在且非空的来源:
localIcon存在时放在第 1 位。remoteIcon存在时放在第 2 位。- 占位图不放入候选列表,而是在候选耗尽后单独回退。
这样可以避免:
- 本地图标成功时还额外发起线上请求。
- 图标字段为空时出现无意义的重试。
状态重置规则
当 props.item 变为新的更新项对象时:
- 重置当前候选索引到第 1 项。
- 清空“候选已耗尽”的状态。
- 重新开始本地优先的尝试流程。
这样可确保列表复用或重新渲染时,新条目不会继承上一条目的失败状态。
占位图规则
保留当前组件内默认占位 SVG,不改样式和尺寸。
只有在以下情况下才使用占位图:
localIcon和remoteIcon都不存在。localIcon加载失败且remoteIcon不存在。localIcon和remoteIcon都加载失败。
错误处理
- 本地图标路径不存在或不可读:允许浏览器触发加载失败,再由前端切到线上图标。
- 远程图标返回 404、超时或其他加载错误:前端切到占位图,不向用户弹额外错误。
- 后端无法推断
category或arch:允许remoteIcon为空,前端只尝试本地图标和占位图。 - 任一图标来源失败都不能影响更新列表正文、状态标签和进度条显示。
测试方案
后端测试
扩展 src/__tests__/unit/update-center/icons.test.ts:
- 本地图标可解析时,
resolveUpdateItemIcons()返回localIcon,并在条件满足时同时包含remoteIcon。 - 本地图标缺失时,仍可返回
remoteIcon。 - 缺少
category或arch时,不返回remoteIcon。 - 两者都不可得时,返回空对象。
组件测试
扩展 src/__tests__/unit/update-center/UpdateCenterItem.test.ts:
- 有
localIcon时先渲染本地file://地址。 - 本地图标未失败前,不切换到
remoteIcon。 - 本地图标触发
error后切到remoteIcon。 - 本地和线上都触发
error后切到默认占位图。 - 切换到新的
item后,回退状态会重置。
风险与约束
- 如果某些包的本地图标路径在后端看来存在,但渲染进程实际不可访问,仍会触发一次失败请求;这是预期行为,因为它正是继续尝试线上图标的触发条件。
- 远程图标 URL 继续依赖当前商店路径规则,若个别包没有线上图标,最终仍会使用占位图。
- 本次只调整更新中心图标链路,不同步抽象其他页面,避免扩大改动范围。
决策总结
- 用
localIcon和remoteIcon替代单个icon字段。 - 主进程负责解析来源,渲染层负责按顺序加载和失败回退。
- 固定回退顺序为:本地图标 -> 线上图标 -> 默认占位图。
- 本地图标成功时,不再加载线上图标和默认图标。