feat(app-iced): M114 账户页三轨登录(微软正版 / 第三方皮肤站 / 本地档案)

- 账户页:当前账户卡 + 全部账户列表 + 空态三入口 + 皮肤区;五项动作(添加/切换/删除/重新登录/校验)
- 三轨添加流程:设备码(用户码+链接+倒计时+过期重试+取消)、授权码(浏览器+可复制链接+取消)、
  本地档案(名字校验)、第三方站(服务地址 + 服务发现校验 + 密码登录,密码掩码且用完即弃)
- 导航:账户为一等页,入口是轨底头像位(未登录=描边剪影 / 已登录=首字母块+名字;窄高收成图标条)
- 凭据红线:DTO 侧形状脱敏 + 状态层二次脱敏 + 密码掩码;Inject-JWT 测试断言视图模型干净(已做变异取证)
- presenter:authlib 登录/服务发现/皮肤抓取(复用下载引擎的 HTTP 客户端,无新增依赖)、
  皮肤原图经 DTO 到 UI 裁剪成头像(既有 image 依赖,未改 Cargo.toml)
- i18n:新增 account 域 51 条中英文案(离线档案口径全量写入),金表同步
This commit is contained in:
AskaEth committed 2026-11-05 05:08:35 +08:00
1 parent e7b04c4afe
commit 1ff00bbe50
43 files changed
+4343 -117

No files matched your search

+29 -10
View File
@@ -21,13 +21,14 @@ mod nav;
/// 用 `#[path]` 挂到外壳的命名空间。
///
/// 为什么是 `#[path]` 而不是在 `pages/mod.rs` 加一行 `mod agent;`:`pages/mod.rs` 是四条页面线
/// 共用的声明文件(本 mission 的 scope 没给它),而右栗是**外壳的一部分**(它挂在任何页面右边)。
/// 共用的声明文件(M104 的 scope 没给它),而右栏是**外壳的一部分**(它挂在任何页面右边)。
/// 先例是 `app/mod.rs` 挂 `../components/mod.rs`。物理路径只有一份,逻辑名字有两个用途:
/// 宿主(右栗)归外壳,视图(会话组件)归页面目录。
/// 宿主(右栏)归外壳,视图(会话组件)归页面目录。
#[path = "../pages/agent/mod.rs"]
pub(crate) mod agent;
pub use nav::NavItem;
// M114:轨底账户头像位的状态也归外壳(`NavItem` + `RailAccount` 一起导出)。
pub use nav::{NavItem, RailAccount};
use iced::widget::text::LineHeight;
use iced::widget::{button, column, container, mouse_area, row, space, stack, text};
@@ -47,17 +48,34 @@ use crate::cli::ForcedState;
use crate::icons;
use crate::theme::Ui;
/// 整棵树(**内容槽位版**):外壳画标题栏 / 缩放热区 / 导航轨 / 右栗,中间那块交给调用方。
/// 整棵树(**内容槽位版**):外壳画标题栏 / 缩放热区 / 导航轨 / 右栏,中间那块交给调用方。
///
/// 产品页的唯一入口(M90 r2 / P1-1;M104 起多两样东西:右侧助手栏与拖拽取指针层;
/// M114 起导航轨还会画轨底的账户头像位)。
///
/// 分工写死:
///
/// - 本模块负责:标题栏(拖动面 = **整条 48px 栏里从窗口左缘到第一个 caption** 的 `mouse_area`,
/// 没有单独的 8px 热区;三个 caption 键各 46×48)、东/南两条缩放热区(各 `Space::X1` = 4px)、
/// 导航轨、右栏、根容器底色;
/// - 调用方负责:`content`(页面内容区)。它拿到的可用宽度是 `窗口宽 − rail 88 − 右栏`,
/// 而页面自己的 `layout::frame_metrics` 仍以**中栏可用宽**为输入(M104 的 `content_width`)。
///
/// 面板 α(D36 / M93)不从这里传:它是**表面色**的属性,唯一施加点是 `Ui::layer`
/// (`crate::theme`)。外壳的根容器 / 标题栏 / 导航轨都走 `ui.layer(..)`,因此自动同源。
///
/// 产品页入口:**内容槽位 + 背景层 + 右栏 + 拖拽层 + 轨底账户头像位**。
///
/// `background` 由 `pages::appearance::layer` 产出(已与 `Message::Page` 映射过);垫在 `stack!`
/// 最底层,于是「面板 α < 1」时能真的看见它——这就是 D34 在像素上的落点。
// 9 个形参:M104 的 `right`/`overlay` 之后 M106 又加了 `lang`(导航轨标签要按语言取词)。
// 拆成结构体要动 `app/view.rs` 的两处调用点,而那正是 M104/M105 反复设锚点的地方——
// 等 M105 那轮 rebase(`draft` 与 `lang` 两个形参都要留)时一并结构体化,先就地 `allow`。
// 10 个形参:M104 的 `right`/`overlay`、M106 的 `lang`(导航轨标签按语言取词)、
// M114 的 `account`(轨底账户头像位状态)。拆成结构体要动 `app/view.rs` / `account_page.rs`
// 的调用点,而那正是 M104/M105/M109 反复设锚点的地方——先就地 `allow`,等下次合流再结构体化。
#[allow(clippy::too_many_arguments)]
pub fn view_with_background<'a>(
ui: &'a Ui,
selected: NavItem,
account: &nav::RailAccount,
app_title: &'a str,
background: Option<Element<'a, Message>>,
content: Element<'a, Message>,
@@ -66,10 +84,11 @@ pub fn view_with_background<'a>(
forced: Option<ForcedState>,
lang: Lang,
) -> Element<'a, Message> {
// 右栗(M104)是 `row` 的**第三个兄弟**而不是叠层:它是布局宽度的一部分,压在上面会让
// 中栗以为自己还有那么宽(那就是任务里禁止的「溢出」)。它的宽度由 `agent::docked` 自己声明。
// 右栏(M104)是 `row` 的**第三个兄弟**而不是叠层:它是布局宽度的一部分,压在上面会让
// 中栏以为自己还有那么宽(那就是任务里禁止的「溢出」)。它的宽度由 `agent::docked` 自己声明。
// M106:导航轨标签按当前界面语言取词(`lang` 一路从 `App::lang()` 递进来)。
let mut work = row![nav::rail(ui, selected, forced, lang), content]
// M114:`account` 只影响轨底那一格,`right`/`overlay` 的语义一字未动。
let mut work = row![nav::rail(ui, selected, account, forced, lang), content]
.width(Length::Fill)
.height(Length::Fill);
if let Some(right) = right {