feat(app-iced): M107 DLVRAIL —— 版本管理页三栏侧栏 + 跨类型搜索

用户令(信息架构):这一页从「下载」改称「版本管理」,版面从「一排 tab + 平铺列表」
改成**页面内三栏**:版本类型栏 + 主版本栏 + 列表/详情,并加搜索框。

- **三栏几何**(`pages/layout/rails.rs`,纯函数 + 数值表):类型栏 140/64、主版本栏 140/64、
  间距 24、列表列下限 424(行宽红线)。四档由**可用宽**裁决:Wide ≥888 / Narrow ≥676 /
  Compact ≥600 / Chips <600,三个门槛都是 `栏宽 + 间距 + 424` 算出来的,不是拍的
- **主版本分组规则**(`download/group.rs`):正式版/预发布按 `major.minor`(`1.21.9-pre3` → `1.21`)、
  周更按年桶(`24w14a` → `24w`)、远古版**没有主版本**(`rd-132211`/`a1.2.6` → 第二级只有「全部」);
  判据用真实版本样本钉住,不按「第一个点」切
- **跨类型搜索**(`search_bar` + 清空 + 命中数):查询词写进 `AppState.download_query`(页面无状态,
  只能这么活),`hits` 是**跨分类**读数;三条口径分开:「有命中」/「没有匹配」/「命中在别的类型里」
- 两级侧栏都用 `navigation::list_item` / `tabs` / `chip` / `buttons::icon`:Tab 可达、hover/pressed/
  选中态全部来自组件,页面不自己写状态层
- 右栏展开(M104 `content_width` 852)⇒ 可用宽 716 ⇒ Narrow 档,列表 464(任务书那句 492 漏扣
  96px 的页边距与栏间距,报告里给了完整数值表)
- 文案 11 条中英进 `crates/i18n`(新域宏接在装配链末尾 ⇒ `Key::ALL` 只追加,金表只追加)
- 护栏:两层侧栏都存在 + 列表按第二级选中态过滤(变异可证)、三档门槛、424 下限扫描、
  版本行单行宽账、搜索条真的装着
- `install.rs` 只动了「已装」分支(加一句「已安装」解释禁用按钮),没碰 M105 的选项区

取证:`DevDocs/artifacts/m107/`(10 张:三档 × 浅深 + 搜索命中 + 空 + 异地 + 远古版)
This commit is contained in:
AskaEth committed 2026-11-05 01:59:13 +08:00
1 parent 72b0c9d465
commit 89a51a63b1
24 files changed
+2000 -340

No files matched your search

+40 -2
View File
@@ -20,7 +20,12 @@
//!
//! 取值:`ready`(缺省)/ `loading` / `empty` / `selected` / `category-release` / `installing` /
//! `installing-late` / `installing-empty` / `failed` / `done` / `done-next` /
//! **`options` / `options-fabric` / `options-incompat`**(M105 的安装选项区三态)。
//! **`options` / `options-fabric` / `options-incompat`**(M105 的安装选项区三态)/
//! **`category-ancient` / `search` / `search-major` / `search-empty` / `search-elsewhere`**
//! (M107 的两级侧栏与搜索取样点)。
//!
//! M107 那几个取样点**都走产品那条真转换**(`Download::from_snapshot`,查询词写在快照的
//! `download_query` 里):图证明的是产品路径的行为,不是演示的特权。
//!
//! `category-release` / `done` / `done-next` 都**不手搓结果**,而是拿一份 `AppState` 跑
//! [`Download::from_snapshot`]:图证明的是产品路径的行为(分类本地过滤、装完后面板让回列表),
@@ -92,6 +97,13 @@ pub fn demo(catalog: Catalog) -> Download {
// 安装进行中、但列表被过滤空:面板要跟着占位态一起在(复审 p3 的边角)
// 分类筛选(本页本地过滤:真 host 丢弃 ListVersions 的分类参数)——混合清单里只留正式版
Some("category-release") => category(catalog, Category::Release),
// M107 的两级侧栏 + 搜索:这三种状态**都走产品那条真转换**(from_snapshot),
// 图里看到的是页面真会画的东西,不是演示的特权。
Some("category-ancient") => searched(catalog, Category::Ancient, ""),
Some("search") => searched(catalog, Category::All, "1.20"),
Some("search-major") => majored(searched(catalog, Category::All, ""), "1.21"),
Some("search-empty") => searched(catalog, Category::All, "zzz"),
Some("search-elsewhere") => searched(catalog, Category::Release, "24w"),
Some("installing-empty") => Download {
install: Some(install(InstallStage::Downloading, 0.42, catalog)),
..Download::loading()
@@ -100,7 +112,7 @@ pub fn demo(catalog: Catalog) -> Download {
}
}
/// 有数据:六行(三条正式、一条预览、两条远古),与 presenter 的 `mock_versions` 同形。
/// 有数据:八行(四条正式、两条预览、两条远古),覆盖四种版本形态与三个主版本桶。
///
/// 不复用 `mock_data::mock_versions()`:演示数据是**出图的基线**,得比 mock 稳定
/// (mock 随 P3 的假数据一起漂)。
@@ -114,11 +126,16 @@ fn ready(catalog: Catalog) -> Download {
Download {
// 混合分类 → 没有单个 chip 该亮(这正是 `Category::of` 的反推结果)
category: Category::of(&versions),
buckets: super::group::buckets(&rows),
hits: rows.len(),
rows,
selected: None,
options: None,
install: None,
loaded: true,
query: String::new(),
selected_major: None,
type_rail_folded: false,
}
}
@@ -147,6 +164,9 @@ fn options(catalog: Catalog, version: &str, loader: Loader, cached: Option<Vec<&
}
/// 演示用的版本表(`ready` 与 `finished` 共用;与 presenter 的 `mock_versions` 同形)。
///
/// 四种形态各有样本:`1.21.9`(正式)、`1.21.9-pre3`(预发布)、`24w14a`(周更)、
/// `rd-132211` / `a1.2.6`(远古,没有主版本)。
fn demo_versions() -> Vec<VersionSummary> {
fn summary(id: &str, kind: VersionKind, released: &str, installed: bool) -> VersionSummary {
VersionSummary {
@@ -161,7 +181,9 @@ fn demo_versions() -> Vec<VersionSummary> {
summary("1.21.9", VersionKind::Release, "2026-09-20", false),
summary("1.21.8", VersionKind::Release, "2026-07-11", true),
summary("1.20.4", VersionKind::Release, "2023-12-07", true),
summary("1.20.1", VersionKind::Release, "2023-06-12", false),
summary("1.21.9-pre3", VersionKind::Snapshot, "2026-09-01", false),
summary("24w14a", VersionKind::Snapshot, "2024-04-03", false),
summary(
"rd-132211",
VersionKind::Ancient(AncientKind::Classic),
@@ -214,6 +236,22 @@ fn category(catalog: Catalog, wanted: Category) -> Download {
Download::from_snapshot(&state, wanted, &InstallDraft::default(), catalog)
}
/// 「用户在搜索框里敲了几个字符」的演示:查询词写进**快照字段**(产品路径就是这么活的)。
fn searched(catalog: Catalog, wanted: Category, query: &str) -> Download {
let mut state = AppState::new();
state.apply(AppEvent::Core(CoreEvent::VersionListChanged(
demo_versions(),
)));
state.download_query = query.to_string();
Download::from_snapshot(&state, wanted, &InstallDraft::default(), catalog)
}
/// 「用户点了第二级侧栏里的一个主版本桶」的演示(纯视图态,与 `on_download` 的落点一致)。
fn majored(mut model: Download, key: &str) -> Download {
model.selected_major = Some(key.to_string());
model
}
/// 一次安装的演示快照(文件计数 / 字节 / 速度 / 剩余时间都给真值,进度条画得出来)。
fn install(stage: InstallStage, ratio: f32, catalog: Catalog) -> InstallView {
let total = 512;