//! 内容库的状态机(M109):结果落地与条件提交两件事。 //! //! 从 `state.rs` 拆出(那个文件重新合流 M105 之后贴着 400 行上限)。放在 `impl AppState` 的 //! **另一个 impl 块**里,路径与可见性都不变:`apply_core` 直接调 `self.apply_content(..)`。 use crate::dto::{ContentDetail, ContentFailure, ContentQuery, ContentResults}; use super::AppState; impl AppState { /// 内容库结果落地(M109):同条件换页 ⇒ **追加**;换条件 ⇒ **整批替换**;迟到 ⇒ 丢掉。 /// /// 这三条就是「列表画的永远是当前筛选条件」的全部保证——页面自己没有过滤逻辑,也没有第二份真相。 pub(super) fn apply_content(&mut self, results: ContentResults) { self.content.pending = false; self.content.failed = None; if !self.content.accepts(&results) { // 迟到的一批:用户已经改了条件,覆盖上去就会出现「筛选条说 A、列表是 B」。 return; } // **先**按「这一页装没装满」记一位,再把它并进累计列表(复审 P1-2:判据不能读累计值)。 self.content.last_page_filled = results.has_more(); match &mut self.content.results { Some(current) if results.query.offset > 0 => { current.hits.extend(results.hits); current.total = results.total; current.curseforge = results.curseforge; current.query = results.query; } _ => self.content.results = Some(results), } } /// 检索失败落地(M162 起带大类):清掉「在飞」并记下失败。 pub(super) fn apply_content_failure(&mut self, failure: ContentFailure) { self.content.pending = false; self.content.failed = Some(failure); } /// 详情事件落地(M162):`Ok` 走 `apply_detail`、`Err` 走 `fail_detail`,两者都按 /// **请求时的项目 id** 判迟到(用户可能已经切到别的项目了)。 pub(super) fn apply_content_detail( &mut self, project: &str, result: Result, ) { match result { Ok(detail) => self.content.apply_detail(project, detail), Err(failure) => self.content.fail_detail(project, failure), } } /// 记下新的检索条件并进入「请求在飞」态(指令由调用方随后 dispatch)。 pub fn set_content_query(&mut self, query: ContentQuery) { self.content.pending = true; self.content.failed = None; if query.offset == 0 { self.content.last_page_filled = false; // 换条件(不是换页):先把旧结果清掉,否则加载态会画出上一批命中项。 self.content.results = None; } self.content.query = query; } }