feat(app-iced): M108 下载页显示名改成「版本管理 / Version management」

用户令(2026-11-04):当前下载页改名叫版本管理,下载页只负责下载整合包和 Mod/光影。
本 mission 只改显示层,标识符(Route::Download / Screen::Download / NavItem::Download /
文件名)一个都没动,标识符改名另登 finding 后置。

i18n(catalog/nav.rs + catalog/download.rs,金表同步):
- NavDownload = Versions/版本管理 —— 88px 收起轨用短名
- NavDownloadExpanded = Version management/版本管理 —— 240px 展开抽屉用全名
- DownloadTitle = Version management/版本管理 —— 页标题
- DownloadSubtitle = Manage versions and loaders: install, upgrade, repair/
  管理版本与加载器:安装、升级、修复
拆两条 key 的理由:18 字符英文 ≈101px 塞不进 88px 轨(实测两通道都被裁:产品通道只显示
Version,组件版轨被切成 Version manage);中文 4 字实测 48.0px(四种 CJK 字体同值)放得下,
所以中文两条同值、不缩。

- pages/nav.rs:NAV 第 3 列 &str → Key;label_key(label, expanded) 是两态切换点
  (用 bool 参数而非 enum:240 态在产品里还没有渲染路径,枚举变体永远构造不到会被
  -D warnings 的 dead_code 拦下)。
- shell/nav.rs:NavItem::Download 显示 Versions(产品路径的轨;i18n 接线归 M106,
  本 mission 只换文案值)。
- 守门:pages/tests.rs 断言导航/页标题取值 + 两态切换 + 目录里凡指向这一页的文案都不许再说
  「下载」(豁免 DownloadStageDownloading);shell/nav.rs 断言标签是 Versions 且 ≤12 字符。
This commit is contained in:
AskaEth committed 2026-11-05 02:54:04 +08:00
1 parent c31036dea8
commit bbf96de9b3
7 files changed
+235 -21

No files matched your search

+3
View File
@@ -2,6 +2,9 @@
//!
//! 用户令(信息架构调整)把这一页从「下载」改称「版本管理」:下载页只负责整合包与 Mod/光影,
//! 而这一页管的是**游戏版本与加载器安装**。本文件是那次改版后的页面装配点。
//! **标识符不改**(用户令 2026-11-04 的「只改显示层」边界):`Route::Download` /
//! `Screen::Download` / 文件名 / 类型名一律保留,全仓重命名另开 finding 后置——所以本文件里
//! 「下载页」三个字到处都是,那是标识符的叫法,不是界面上出现的词。
//! **M102 版面订正**:旧版把整页块压到 424 并水平居中(1180 宽下左缘 422、页头标题在 112,
//! 左右各空 ~300px,视觉复审判为「内容缩在中间一条窄栏」)。现在页面块走 [`frame::body`]
//! ——左缘与页头标题同一条线(= 页边距 24),宽 = 块宽 `frame::body_width`(1180 下 1044,
+142 -9
View File
@@ -15,6 +15,7 @@
use iced::Element;
use unlucky_design::icons::IconKind;
use unlucky_i18n::{Key, Lang};
use unlucky_presenter::router::{NavSection, Route};
use crate::app::components::navigation::drawer::{self, Navigation};
@@ -23,44 +24,80 @@ use crate::theme::Ui;
use super::Message;
/// 导航轨的固定项:图标 + 标签 + 目标路由(顺序 = 自上而下)。
/// 导航轨的固定项:图标 + 文案 key + 目标路由(顺序 = 自上而下)。
///
/// 顺序与 `presenter::router::NavSection` 的声明顺序一致,改这里等于改导航栏。
///
/// 第 3 列是 [`Key`] 而不是字面量(M108):导航文案归目录管,`pages_demo` 出的图与将来的中文
/// 界面读的是同一份表,不会出现「页面翻译了、导航还是硬编码英文」。
///
/// **显示名 ≠ 标识符**:`Key::NavDownload` 这条说的是「版本管理 / Versions」,而它指向的仍是
/// `Route::Download`(用户令只改显示层,标识符改名后置)。
///
/// 表里存的是**收起态(88)的 key**;展开态(240)要对 [`label_key`] 过一道才换全名。
/// 可见性是 `pub(crate)`:`pages/tests.rs` 的三条断言读它(M99 从 `pages/mod.rs` 搬出来时,
/// 它从「模块私有」变成「crate 内可见」,断言一条没动、强度也没降)。
pub(crate) const NAV: [(Option<NavSection>, IconKind, &str, Route); 5] = [
pub(crate) const NAV: [(Option<NavSection>, IconKind, Key, Route); 5] = [
(
Some(NavSection::Instances),
IconKind::Instances,
"Instances",
Key::NavInstances,
Route::Instances,
),
(
Some(NavSection::Download),
IconKind::Download,
"Download",
Key::NavDownload,
Route::Download,
),
(Some(NavSection::Logs), IconKind::Logs, "Logs", Route::Logs),
(
Some(NavSection::Logs),
IconKind::Logs,
Key::NavLogs,
Route::Logs,
),
(
Some(NavSection::Settings),
IconKind::Settings,
"Settings",
Key::NavSettings,
Route::Settings,
),
// AI 独立页没有导航高亮项(`Route::nav()` 对它返回 `None`)
(None, IconKind::Ai, "Assistant", Route::Agent),
// AI 独立页没有导航高亮项(`Route::nav()` 对它返回 `None`);文案用 M106 给导航项新开的
// `Key::NavAi`(与产品路径的 `shell/nav.rs` 同一条 key,两条 rail 不许各借一个词)。
(None, IconKind::Ai, Key::NavAi, Route::Agent),
];
/// 这一项在当前宽度态下的文案 key。
///
/// `expanded` = D33 的 240 展开抽屉(用全名),`false` = 88 收起轨(用短名)。产品路径**当前只有
/// 88 收起态**,所以调用点只出现 `false`。
///
/// **为什么是 bool 参数而不是 `enum NavWidth { Rail, Drawer }`**:D33 的抽屉还没有渲染路径,
/// 枚举里那个 `Drawer` 变体在产品代码里永远构造不到,`-D warnings` 会按 `dead_code` 把它拦下来
/// (M108 第一版就是这么红的)。参数化之后没有「构造不到的状态」,切换点仍只有这一处。
pub(crate) const fn label_key(label: Key, expanded: bool) -> Key {
if expanded && matches!(label, Key::NavDownload) {
Key::NavDownloadExpanded
} else {
label
}
}
/// 导航轨:**真组件**(`components::navigation` 的 rail 形态,宽 = 令牌 `nav_rail_width` = 88)。
///
/// M84 这里是一条留白带;M90 换成 M78 的 `NavigationRail`,于是「导航项 → 路由」是真的可点。
///
/// 语言暂时固定 [`Lang::En`]:组件版 rail 的调用点只有 `pages/mod.rs` 的 `nav::rail(ui, model.active)`,
/// 那里还没把 `Model.lang` 传进来(`pages/mod.rs` 不在 M108 的 scope 内)。文案本身已经走 [`Key`],
/// 接线时只改这一行——产品路径那边同一笔欠账归 M106(它给 `shell/nav.rs` 接 i18n)。
pub(super) fn rail<'a>(ui: &'a Ui, active: Option<NavSection>) -> Element<'a, Message> {
let catalog = Lang::En.catalog();
let mut group = drawer::NavGroup::new();
for (section, icon, label, route) in NAV {
let mut item = drawer::NavItem::new(label, icon, Message::Navigate(route));
// 88 收起轨 = 短名(240 展开抽屉才用全名,见 [`label_key`])
let label = label_key(label, false);
let mut item = drawer::NavItem::new(catalog.text(label), icon, Message::Navigate(route));
if section.is_some() && active == section {
item = item.selected();
}
@@ -69,3 +106,99 @@ pub(super) fn rail<'a>(ui: &'a Ui, active: Option<NavSection>) -> Element<'a, Me
navigation(ui, Navigation::rail().group(group))
}
#[cfg(test)]
mod tests {
//! 导航表与文案的守门(M108)。
//!
//! 这条断言原先挂在 `pages/tests.rs`,但那份文件在 M102 合并后是 464 行、撞了「单文件 ≤400 行」
//! 的硬门禁(`components/gate.rs` 按全仓判红),于是按「谁的数据谁守」挪到本文件——`NAV` 表在
//! 这里,守门也在这里,强度一条没降。
use super::*;
/// **M108**:这一页对外叫「版本管理」,代码里仍是 `Route::Download`。
///
/// 用户令(2026-11-04)只要**显示层**改名:全仓重命名标识符会与在途的多条线全面撞车,
/// 所以 `Route::Download` / `NavSection::Download` / `Screen::Download` / 文件名一个都不动。
///
/// 英文有两个不同长度的名字,**是刻意的**(见 `catalog/nav.rs` 的「两条 key」一节):
/// 88px 收起轨放不下 `Version management`(≈101px),用短名 `Versions`;页标题与 240 展开抽屉
/// 用全名。中文两处都是「版本管理」(≈48px,放得下就不缩)。
///
/// **抓得住**:① 导航项或页标题又写回「下载」「Download」;② 88 轨上误用全名(会截断/换行);
/// ③ 副标题退回「只说下载」;④ 中文被「为了对称」砍短。**抓不住**:产品路径的导航轨
/// (`shell/nav.rs` 是另一套手搓实现,它自己有一条同样的断言)、真画出来会不会截断(那要出图)、
/// 中文目录在真机上的字体(截图通道固定 En,见 M108 报告与 finding)。
#[test]
fn the_download_route_is_presented_as_version_management() {
let en = Lang::En.catalog();
let zh = Lang::ZhCn.catalog();
let entry = NAV
.iter()
.find(|entry| entry.3 == Route::Download)
.expect("导航表里有版本管理页那一项");
assert_eq!(entry.2, Key::NavDownload, "导航文案走目录,不写字面量");
// 88 收起轨 = 短名;240 展开与页标题 = 全名(中文两处同值)
assert_eq!(en.text(Key::NavDownload), "Versions");
assert_eq!(en.text(Key::NavDownloadExpanded), "Version management");
assert_eq!(en.text(Key::DownloadTitle), "Version management");
assert_eq!(zh.text(Key::NavDownload), "版本管理");
assert_eq!(zh.text(Key::NavDownloadExpanded), "版本管理");
assert_eq!(zh.text(Key::DownloadTitle), "版本管理");
assert_eq!(
label_key(Key::NavDownload, false),
Key::NavDownload,
"88 收起态必须用短名"
);
assert_eq!(
label_key(Key::NavDownload, true),
Key::NavDownloadExpanded,
"240 展开态换全名"
);
assert_eq!(
label_key(Key::NavLogs, true),
Key::NavLogs,
"其余四项两态同一条 key"
);
// 目录里凡是指向这一页的文案都不许再说「下载」。唯一豁免是 `Downloading files`:
// 那是安装流程里真在传文件的那一步,说的是当时的动作、不是页面身份。
let page_keys = Key::ALL.iter().filter(|key| {
(key.name().starts_with("Download") && **key != Key::DownloadStageDownloading)
|| **key == Key::NavDownload
|| **key == Key::NavDownloadExpanded
});
let mut checked = 0;
for key in page_keys {
assert!(
!en.text(*key).to_ascii_lowercase().contains("download"),
"{} 的英文文案还在说「下载」:{}",
key.name(),
en.text(*key)
);
assert!(
!zh.text(*key).contains("下载"),
"{} 的中文文案还在说「下载」:{}",
key.name(),
zh.text(*key)
);
checked += 1;
}
assert!(checked > 20, "只扫到 {checked} 条,守门形同虚设");
// 副标题要讲这个页面「管什么」(版本 + 加载器),不是「能下载什么」
let subtitle_en = en.text(Key::DownloadSubtitle);
let subtitle_zh = zh.text(Key::DownloadSubtitle);
assert!(
subtitle_en.contains("versions") && subtitle_en.contains("loaders"),
"副标题要提到版本与加载器:{subtitle_en}"
);
assert!(
subtitle_zh.contains("版本") && subtitle_zh.contains("加载器"),
"副标题要提到版本与加载器:{subtitle_zh}"
);
}
}
+5 -2
View File
@@ -7,6 +7,7 @@ use super::nav::NAV;
use super::*;
use unlucky_design::color::SchemeFlavor;
use unlucky_design::typography::Platform;
use unlucky_i18n::Lang;
use unlucky_presenter::router::{NavSection, Route};
use crate::theme::Families;
@@ -334,13 +335,15 @@ fn routes_map_onto_pages() {
#[test]
fn every_rail_item_points_at_a_route_that_highlights_it() {
assert_eq!(NAV.len(), 5, "导航轨 5 项(4 个一级页 + AI)");
let catalog = Lang::En.catalog();
for (section, _icon, label, route) in NAV {
assert!(!label.is_empty());
assert!(!catalog.text(label).is_empty(), "{} 没有文案", label.name());
assert_eq!(
route.nav(),
section,
"{label} 指向的 {route:?} 高亮的是 {:?}",
"{} 指向的 {route:?} 高亮的是 {:?}",
label.name(),
route.nav()
);
}
+27 -1
View File
@@ -34,7 +34,7 @@ use crate::theme::Ui;
pub enum NavItem {
/// 实例列表。
Instances,
/// 下载/版本。
/// 版本管理(显示名;变体名按用户令「只改显示层」不改,标识符改名后置)。
Download,
/// 日志。
Logs,
@@ -274,4 +274,30 @@ mod tests {
assert!(!item.icon().icon().path.is_empty(), "{item:?} 没有图标");
}
}
/// **M108**:导航栏上这一项叫 `Versions`(88px 轨放不下全名 `Version management`)。
///
/// 文案本身走目录(M106 的 `label_key()` + `label(lang)`),这条只守**取值**与**宽度余量**:
///
/// - **抓得住**:① 有人把 `Key::NavDownload` 的英文改回「下载」或塞回 18 字符的全名
/// (88px 轨上会截断/换行,实测全名 ≈101px);② 中文被「为了对称」砍成两个字。
/// - **抓不住**:真画出来的截断(那要出图,见 M108 报告里的产品通道与 pages_demo 图);
/// 也抓不住 240 展开抽屉(那一档在产品里还不可达)。
#[test]
fn the_download_item_is_labelled_version_management() {
// 88px 轨的余量:中文 4 字 ≈48px、英文短名 ≈45px,都该有明显的余量
assert_eq!(NavItem::Download.label(Lang::En), "Versions");
assert_eq!(NavItem::Download.label(Lang::ZhCn), "版本管理");
for item in NavItem::ALL {
let label = item.label(Lang::En);
assert!(
!label.to_ascii_lowercase().contains("download"),
"{item:?} 的英文标签还在说下载:{label}"
);
assert!(
label.chars().count() <= 12,
"{item:?} 的标签太长,88px 的轨放不下:{label}"
);
}
}
}