:護照收藏與進度統計)
一、把探索行為收斂成一份可恢復的進度文化知識應用中的“收藏”并不只是給卡片切換一個圖標。山海萬靈把一次神獸發現同時映射到圖鑒收錄、地域探索比例、護照印章和下次進入時的區域位置。如果這些結果由各頁面各自保存重復發現、切換區域和重啟恢復很容易出現不一致。工程把可變的探索數據定義為ShanhaiLocalProgress版本號、已發現神獸 ID、已選地域、已選展廳與更新時間放在同一份記錄中。圖鑒資料、地域資料和展廳資料仍然屬于啟動數據不與用戶進度混存。這樣重置進度只影響本機探索記錄不會刪除知識內容或遠端資料。export interface ShanhaiLocalProgress { schemaVersion: number; discoveredIds: string[]; selectedRegionId: string; selectedHallId: string; updatedAt: number; } export function createEmptyShanhaiLocalProgress(): ShanhaiLocalProgress { return { schemaVersion: SHANHAI_LOCAL_PROGRESS_SCHEMA_VERSION, discoveredIds: [], selectedRegionId: , selectedHallId: , updatedAt: 0 }; }二、Preferences 只保存原子字段領域狀態保持完整形狀倉儲以shanhai_local_progress作為 Preferences 文件名分別寫入 schema、發現 ID 串、地域 ID、展廳 ID 和更新時間。ID 在寫入前去重并以分隔符序列化讀取失敗、類型不符或存儲不可用時返回空進度而不是把異常值直接交給頁面。async save(progress: ShanhaiLocalProgress): Promisevoid { const store: preferences.Preferences | null await this.getStore(); if (store null) { return; } await store.put(KEY_SCHEMA_VERSION, SHANHAI_LOCAL_PROGRESS_SCHEMA_VERSION); await store.put(KEY_DISCOVERED_IDS, this.serializeIds(progress.discoveredIds)); await store.put(KEY_SELECTED_REGION_ID, progress.selectedRegionId); await store.put(KEY_SELECTED_HALL_ID, progress.selectedHallId); await store.put(KEY_UPDATED_AT, progress.updatedAt); await store.flush(); }這里有兩個邊界需要保留。第一flush()使一次用戶動作對應一次明確的落盤完成點第二倉儲層吞掉的是持久化異常頁面仍可繼續瀏覽當前內容并由同步狀態提示后續動作。頁面不需要知道鍵名或序列化格式。三、發現神獸時一次性更新圖鑒、護照和位置ShanhaiAppViewModel.discoverBeast是狀態更新入口。它先判斷目標是否存在再判斷是否已經發現新發現的 ID 只追加到復制后的數組。隨后把神獸所屬地域和展廳寫為當前探索位置持久化后用啟動數據與本地進度重新生成聚合結果。const alreadyDiscovered: boolean this.localProgress.discoveredIds.indexOf(beast.id) 0; const discoveredIds: string[] this.localProgress.discoveredIds.slice(); if (!alreadyDiscovered) { discoveredIds.push(beast.id); } this.localProgress this.normalizeLocalProgress(bootstrap, { schemaVersion: SHANHAI_LOCAL_PROGRESS_SCHEMA_VERSION, discoveredIds: discoveredIds, selectedRegionId: beast.regionId, selectedHallId: beast.hallId, updatedAt: Date.now() }); await this.persistLocalProgress(); const mergedBootstrap: ShanhaiBootstrap this.applyLocalProgress(bootstrap, this.localProgress);這種順序避免了“頁面先改了計數、存儲失敗后無法恢復”的雙源狀態。重復發現不會再次增加記錄但仍會返回已有發現的提示無效 ID 則保持原進度不變。四、進度和護照印章都由發現記錄推導地域進度不單獨保存百分比。視圖模型遍歷地域的beastIds統計已經發現的數量再計算百分比和“已收錄 x/y”狀態。護照印章也遵循同一條規則一個地域的全部神獸都在discoveredIds中時該地域才進入passportRegionIds。private derivePassportRegionIds(regions: RegionItem[], discoveredIds: string[]): string[] { return regions .filter((region: RegionItem) region.beastIds.length 0 region.beastIds.every((beastId: string) discoveredIds.indexOf(beastId) 0)) .map((region: RegionItem) region.id); } private applyDiscoveryProgressToRegions(regions: RegionItem[], discoveredIds: string[]): RegionItem[] { return regions.map((region: RegionItem): RegionItem { const discoveredCount: number region.beastIds .filter((beastId: string) discoveredIds.indexOf(beastId) 0).length; const progress: number region.beastIds.length 0 ? 0 : Math.round(discoveredCount * 100 / region.beastIds.length); return { ...region, progress: progress, status: 已收錄 discoveredCount / region.beastIds.length }; }); }從同一個事實源派生統計能保證檔案頁的“已發現”和“護照印章”與地圖卡片的區域進度始終對應。地域沒有目錄時進度固定為 0避免除零和虛假的完成印章。五、頁面只消費結果并保留可見的失敗出口本次驗證安裝了當前構建的山海萬靈包在圖鑒中完成九尾狐的發現動作后回到檔案頁。頁面回讀顯示“已發現 3 種神獸”和“護照印章 1 枚”說明發現記錄已經驅動統計和印章結果而不是只改變詳情頁上的局部控件。隨后結束應用進程并重新啟動同一包檔案頁仍保留 3 條發現記錄、1 枚護照印章以及最近探索地域“青丘”。這只驗證本機 Preferences 的恢復鏈路本文不把它擴展為云端同步、跨設備遷移或網絡請求成功的結論。驗收時還應把“發現”和“完成地域”分開觀察前者直接增加 discoveredIds后者要等一個地域目錄中的全部神獸都已收錄才產生護照印章。這樣既能避免單次發現被誤記為地域完成也讓 UI 上的兩個數字能追溯到不同的派生規則。若存儲層暫時不可用應用仍可保留本次會話中的知識瀏覽和狀態展示下一次啟動按空進度恢復不把未落盤的數據偽裝成持久結果。頁面層調用syncDiscovery后回寫discoveredIds、passportRegionIds、地域和展廳數據。新出現的印章會被標記為高亮目標本地 fallback 狀態會轉換為頁面可讀的同步提示。失敗時不偽造成功結果而是保留當前內容并顯示重試路徑。檔案頁把發現數和護照印章數作為明確的Prop傳入統計面板統計組件不讀取 Preferences。這樣窄屏、寬屏和主題切換只影響排版不會改變進度計算。場景進度寫入頁面結果首次發現神獸追加 ID、更新地域與展廳位置圖鑒、地域進度和護照候選一起重算重復發現保留原 ID 集合不重復計數繼續展示關聯探索提示存儲讀取異常使用空進度知識資料仍可瀏覽進度從初始狀態開始本地進度重置清空進度倉儲圖鑒資料、個人檔案和遠端數據保持不變六、驗收要覆蓋恢復、重復動作和局部重置這條鏈路的驗收不應只看一個數字。應先在圖鑒中完成一次發現確認檔案統計、地域進度和護照印章基于同一條記錄更新再重啟應用檢查發現 ID、當前地域和展廳位置是否恢復。隨后重復觸發同一神獸確認計數不增加最后打開本地進度重置確認框檢查取消不會變更記錄確認重置后只回到探索初始狀態。對于網絡數據源不可用的場景繼續檢查本地 fallback 是否保留可瀏覽內容并讓同步橫幅提供重試入口。這樣持久化失敗、無效目標和重復動作都有明確的狀態邊界而不是由頁面臨時補丁掩蓋。七、總結山海萬靈用一份版本化的本地進度記錄承載發現、地域和展廳選擇Preferences 負責持久化原子字段視圖模型負責歸一化、去重和派生統計ArkUI 頁面只展示已經聚合的結果。收藏、護照印章與探索進度因此共享同一條數據鏈路重置也能準確限定在本機探索狀態內。