- 账户页:当前账户卡 + 全部账户列表 + 空态三入口 + 皮肤区;五项动作(添加/切换/删除/重新登录/校验) - 三轨添加流程:设备码(用户码+链接+倒计时+过期重试+取消)、授权码(浏览器+可复制链接+取消)、 本地档案(名字校验)、第三方站(服务地址 + 服务发现校验 + 密码登录,密码掩码且用完即弃) - 导航:账户为一等页,入口是轨底头像位(未登录=描边剪影 / 已登录=首字母块+名字;窄高收成图标条) - 凭据红线:DTO 侧形状脱敏 + 状态层二次脱敏 + 密码掩码;Inject-JWT 测试断言视图模型干净(已做变异取证) - presenter:authlib 登录/服务发现/皮肤抓取(复用下载引擎的 HTTP 客户端,无新增依赖)、 皮肤原图经 DTO 到 UI 裁剪成头像(既有 image 依赖,未改 Cargo.toml) - i18n:新增 account 域 51 条中英文案(离线档案口径全量写入),金表同步
159 lines
4.6 KiB
Rust
159 lines
4.6 KiB
Rust
//! 两条登录流的面板态(微软设备码/授权码 + 第三方站校验/登录)。
|
|
//!
|
|
//! 从 `model.rs` 拆出来守 400 行硬线:这些类型是「UI 要画什么」的枚举,
|
|
//! 判定逻辑(倒计时 → 已过期、服务发现 → 可用)也在这里,页面与表单只消费结果。
|
|
|
|
use unlucky_presenter::dto::{DeviceLoginPhase, DeviceLoginView};
|
|
|
|
/// 微软登录面板要画什么(每态都不同,**不允许只有一个转圈**)。
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum DevicePanel {
|
|
/// 没有登录在跑
|
|
Hidden,
|
|
/// 正在申请设备码
|
|
Requesting {
|
|
/// 状态文案(presenter 给的,已脱敏)
|
|
message: String,
|
|
},
|
|
/// 设备码等待用户操作
|
|
Code {
|
|
/// 用户码
|
|
code: String,
|
|
/// 验证链接
|
|
link: String,
|
|
/// 剩余秒数
|
|
remaining: Option<u64>,
|
|
/// 是否已过期
|
|
expired: bool,
|
|
/// 状态文案
|
|
message: String,
|
|
},
|
|
/// 授权码流:等浏览器回调
|
|
Browser {
|
|
/// 授权链接(可复制)
|
|
link: String,
|
|
/// 剩余秒数
|
|
remaining: Option<u64>,
|
|
/// 是否已过期
|
|
expired: bool,
|
|
/// 状态文案
|
|
message: String,
|
|
},
|
|
/// 失败(原文已脱敏)
|
|
Failed {
|
|
/// 失败原因
|
|
message: String,
|
|
},
|
|
/// 成功
|
|
Succeeded {
|
|
/// 账户名
|
|
name: String,
|
|
},
|
|
}
|
|
|
|
/// 第三方轨面板的可见状态。
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum AuthlibPanel {
|
|
/// 没在跑
|
|
Idle,
|
|
/// 正在校验/登录
|
|
Running {
|
|
/// 状态文案
|
|
message: String,
|
|
},
|
|
/// 地址校验通过(服务发现拿到服务器名)
|
|
Ready {
|
|
/// 服务器名
|
|
server_name: String,
|
|
/// 状态文案
|
|
message: String,
|
|
},
|
|
/// 失败(原文已脱敏)
|
|
Failed {
|
|
/// 失败原因
|
|
message: String,
|
|
},
|
|
/// 成功
|
|
Succeeded {
|
|
/// 账户名
|
|
name: String,
|
|
},
|
|
}
|
|
|
|
/// 微软登录状态 → 面板态(倒计时与「已过期」都在这里定性,UI 只画)。
|
|
pub(super) fn device_panel(view: &DeviceLoginView, remaining: Option<u64>) -> DevicePanel {
|
|
let expired = remaining == Some(0);
|
|
match view.phase {
|
|
DeviceLoginPhase::Idle => DevicePanel::Hidden,
|
|
DeviceLoginPhase::Requesting => DevicePanel::Requesting {
|
|
message: view.message.clone(),
|
|
},
|
|
DeviceLoginPhase::Waiting => {
|
|
if view.user_code.trim().is_empty() {
|
|
DevicePanel::Browser {
|
|
link: view.verification_uri.clone(),
|
|
remaining,
|
|
expired,
|
|
message: view.message.clone(),
|
|
}
|
|
} else {
|
|
DevicePanel::Code {
|
|
code: view.user_code.clone(),
|
|
link: view.qr_link.clone(),
|
|
remaining,
|
|
expired,
|
|
message: view.message.clone(),
|
|
}
|
|
}
|
|
}
|
|
DeviceLoginPhase::Failed => DevicePanel::Failed {
|
|
message: view.message.clone(),
|
|
},
|
|
DeviceLoginPhase::Succeeded => DevicePanel::Succeeded {
|
|
name: view.account_name.clone(),
|
|
},
|
|
}
|
|
}
|
|
|
|
/// 第三方轨状态 → 面板态(顺带取出「校验通过的服务器名」)。
|
|
pub(super) fn authlib_panel(
|
|
view: &unlucky_presenter::dto::AuthlibLoginView,
|
|
) -> (AuthlibPanel, String) {
|
|
match view.phase {
|
|
DeviceLoginPhase::Idle => (AuthlibPanel::Idle, String::new()),
|
|
DeviceLoginPhase::Requesting => (
|
|
AuthlibPanel::Running {
|
|
message: view.message.clone(),
|
|
},
|
|
String::new(),
|
|
),
|
|
DeviceLoginPhase::Waiting => (
|
|
AuthlibPanel::Ready {
|
|
server_name: view.server_name.clone(),
|
|
message: view.message.clone(),
|
|
},
|
|
view.server_name.clone(),
|
|
),
|
|
DeviceLoginPhase::Succeeded => (
|
|
AuthlibPanel::Succeeded {
|
|
name: view.account_name.clone(),
|
|
},
|
|
view.server_name.clone(),
|
|
),
|
|
DeviceLoginPhase::Failed => (
|
|
AuthlibPanel::Failed {
|
|
message: view.message.clone(),
|
|
},
|
|
String::new(),
|
|
),
|
|
}
|
|
}
|
|
|
|
impl AuthlibPanel {
|
|
/// 是否正在跑(此时表单字段禁用,避免改了一半的地址被提交)。
|
|
#[must_use]
|
|
pub fn busy(&self) -> bool {
|
|
matches!(self, Self::Running { .. })
|
|
}
|
|
}
|