Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions devel/0304.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# [0304] 修复启动页推荐模板在无网络时不显示的问题

## 相关文档
- [dddd.md](dddd.md) - 任务文档模板

## 任务相关的代码文件
- `src/Mogan/TemplateCenter/template_cache.hpp`
- `src/Mogan/TemplateCenter/template_cache.cpp`
- `src/Mogan/TemplateCenter/template_manager.cpp`
- `src/Plugins/Qt/QTMHomePage.cpp`

## 如何测试

### 确定性测试(单元测试)
```bash
xmake b stem
```

### 非确定性测试(文档验证)
1. 在有网络环境下启动 Mogan,确认启动页首页显示推荐模板缩略图卡片。
2. 关闭程序,确认 `$TEXMACS_HOME_PATH/system/template_cache/metadata.json`(或对应平台缓存目录)中包含 `recommend_ids` 字段。
3. 断开网络,再次启动 Mogan,确认启动页首页仍然显示推荐模板缩略图卡片。
4. 恢复网络,确认程序自动刷新推荐模板列表。

## 如何提交

提交前执行以下最少步骤:

```bash
xmake b stem
```

## What

修复启动页首页(QTMHomePage)在有缓存但无网络连接时,推荐模板区域不显示的问题。

1. 在 `TemplateCache` 中新增 `loadRecommendIdsCache()` 和 `saveRecommendIdsCache()`,将推荐模板 ID 列表持久化到 `metadata.json` 的 `recommend_ids` 字段。
2. 在 `TemplateManager::initialize()` 中从缓存恢复 `recommendTemplateIds_`,并在 `initialized_= true` 之后发射 `recommendTemplatesLoaded()` 信号。
3. 在 `TemplateManager::onRemoteRecommendTemplatesLoaded()` 成功返回后,调用 `saveRecommendIdsCache()` 保存推荐 ID 列表。
4. 在 `TemplateManager::onNetworkStateChanged()` 中,网络恢复时同时调用 `refreshRecommendTemplates()` 补刷推荐模板。
5. 在 `QTMHomePage` 构造函数中,当 `TemplateManager` 已初始化时,兜底调用 `refreshTemplateCards()` 创建推荐模板卡片。

## Why

启动页首页右侧的样式卡片区包含"New document"、"Open document"以及动态加载的推荐模板缩略图。原始代码存在以下缺陷:

1. `recommendTemplateIds_`(推荐模板 ID 列表)从未被缓存。程序重启后该列表为空,即使 `templates_` 元数据已从缓存恢复,`recommendTemplates()` 仍然返回空列表。
2. `TemplateManager::initialize()` 中 `recommendTemplatesLoaded()` 信号在 `initialized_= true` 之前发射。而 `QTMHomePage::refreshTemplateCards()` 第一行即检查 `mgr->isInitialized()`,此时仍为 `false`,直接返回,导致推荐卡片根本没有被创建。
3. `onNetworkStateChanged(true)` 仅刷新分类列表,不刷新推荐模板。若启动时离线、后续恢复网络,推荐模板永远不会被加载。
4. `QTMHomePage` 构造函数在 `TemplateManager` 已初始化时,仅调用 `refreshTemplateThumbnails()` 加载已有卡片的缩略图,从不主动调用 `refreshTemplateCards()` 创建推荐卡片。

上述问题叠加后,表现为:只要程序重启时网络不可用(或 DNS 失败),即使本地有完整的模板元数据缓存,启动页首页的推荐模板区域也始终空白。

## How

### 缓存层持久化推荐 ID 列表

`template_cache.cpp` 中新增两个方法:

- `loadRecommendIdsCache()`:读取 `metadata.json`,解析 `recommend_ids` 数组,返回 `QStringList`。
- `saveRecommendIdsCache()`:读取现有 `metadata.json`(保留 `templates` 等字段),插入或更新 `recommend_ids` 数组后写回。

### 恢复时保证初始化状态可见

`template_manager.cpp` 的 `initialize()` 调整执行顺序:

```cpp
// 先完成所有本地加载和状态标记
initialized_= true;
emit initialized (true);

// 再恢复推荐 ID 并通知 UI
recommendTemplateIds_= cache_->loadRecommendIdsCache ();
if (!recommendTemplateIds_.isEmpty ()) {
emit recommendTemplatesLoaded ();
}
```

确保 UI 槽函数 `refreshTemplateCards()` 执行时,`isInitialized()` 已经返回 `true`。

### 网络恢复时补刷推荐模板

```cpp
void TemplateManager::onNetworkStateChanged (bool isOnline) {
isOnline_= isOnline;
if (isOnline && initialized_) {
refreshCategories ();
refreshRecommendTemplates ();
}
}
```

### UI 层兜底

`QTMHomePage` 构造函数中,若 `TemplateManager` 已初始化且模板数据非空,主动调用 `refreshTemplateCards()`,避免完全依赖信号:

```cpp
if (mgr->isInitialized () && !mgr->templates ().isEmpty ()) {
refreshTemplateThumbnails ();
refreshTemplateCards ();
}
```
64 changes: 64 additions & 0 deletions src/Mogan/TemplateCenter/template_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,70 @@ TemplateCache::saveMetadataCache (
file.write (doc.toJson (QJsonDocument::Compact));
}

QStringList
TemplateCache::loadRecommendIdsCache () {
QStringList result;

QString cachePath= metadataCachePath ();
if (!QFile::exists (cachePath)) {
return result;
}

QFile file (cachePath);
if (!file.open (QIODevice::ReadOnly)) {
qWarning () << "[Template] Failed to open metadata cache for recommend IDs:"
<< cachePath;
return result;
}

QByteArray data= file.readAll ();
QJsonDocument doc = QJsonDocument::fromJson (data);
if (doc.isNull () || !doc.isObject ()) {
return result;
}

QJsonObject root = doc.object ();
QJsonArray recommendIds= root.value ("recommend_ids").toArray ();
for (const auto& val : recommendIds) {
QString id= val.toString ();
if (!id.isEmpty ()) result.append (id);
}

return result;
}

void
TemplateCache::saveRecommendIdsCache (const QStringList& recommendIds) {
QString cachePath= metadataCachePath ();

// Read existing metadata JSON to preserve it
QJsonObject root;
if (QFile::exists (cachePath)) {
QFile file (cachePath);
if (file.open (QIODevice::ReadOnly)) {
QJsonDocument doc= QJsonDocument::fromJson (file.readAll ());
if (!doc.isNull () && doc.isObject ()) {
root= doc.object ();
}
}
}

QJsonArray idsArray;
for (const QString& id : recommendIds) {
idsArray.append (id);
}
root.insert ("recommend_ids", idsArray);

QJsonDocument doc (root);
QFile file (cachePath);
if (!file.open (QIODevice::WriteOnly)) {
qWarning () << "[Template] Failed to write recommend IDs to metadata cache:"
<< cachePath;
return;
}
file.write (doc.toJson (QJsonDocument::Compact));
}

bool
TemplateCache::isTemplateCached (const QString& templateId) const {
auto it= cacheIndex_.find (templateId);
Expand Down
4 changes: 4 additions & 0 deletions src/Mogan/TemplateCenter/template_cache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ class TemplateCache : public QObject {
QHash<QString, TemplateMetadataPtr> loadMetadataCache ();
void saveMetadataCache (const QHash<QString, TemplateMetadataPtr>& metadata);

// 推荐模板ID缓存
QStringList loadRecommendIdsCache ();
void saveRecommendIdsCache (const QStringList& recommendIds);

// 分类缓存
QList<TemplateCategory> loadCategoriesCache ();
void saveCategoriesCache (const QList<TemplateCategory>& categories);
Expand Down
12 changes: 12 additions & 0 deletions src/Mogan/TemplateCenter/template_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ TemplateManager::initialize () {

initialized_= true;
emit initialized (true);

// Load cached recommend template IDs AFTER initialized_= true so that
// UI callbacks like refreshTemplateCards() can safely query isInitialized().
recommendTemplateIds_= cache_->loadRecommendIdsCache ();
if (!recommendTemplateIds_.isEmpty ()) {
// Emit signal so UI can show cached recommendations immediately;
// do NOT set recommendTemplatesFetched_ here — we still want to
// refresh from network when online.
emit recommendTemplatesLoaded ();
}
}

void
Expand Down Expand Up @@ -505,6 +515,7 @@ TemplateManager::onNetworkStateChanged (bool isOnline) {
isOnline_= isOnline;
if (isOnline && initialized_) {
refreshCategories ();
refreshRecommendTemplates ();
}
}

Expand Down Expand Up @@ -612,6 +623,7 @@ TemplateManager::onRemoteRecommendTemplatesLoaded (

mergeMetadata (metadata, true);
cache_->saveMetadataCache (templates_);
cache_->saveRecommendIdsCache (recommendTemplateIds_);
emit recommendTemplatesLoaded ();
}

Expand Down
1 change: 1 addition & 0 deletions src/Plugins/Qt/QTMHomePage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ QTMHomePage::QTMHomePage (QWidget* parent) : QWidget (parent) {
Qt::UniqueConnection);
if (mgr->isInitialized () && !mgr->templates ().isEmpty ()) {
refreshTemplateThumbnails ();
refreshTemplateCards ();
}
else if (!mgr->isInitialized ()) {
QTimer::singleShot (0, [mgr] () { mgr->initialize (); });
Expand Down
Loading