用 .NET MAUI 10 + VS Copilot 从 0 开发一个签到 App(六)登录
用 .NET MAUI 10 + VS Copilot 从 0 开发一个签到 App(六)
一、本文背景
在前一篇中,我们已经完成了用户注册,解决了一个核心问题:
用户从哪里来?
但一个系统只有注册,没有登录,是无法真正使用的。
因此在真实项目推进中,下一步几乎是必然的:
登录页面 + 登录态建立
这一篇依然遵循同一个原则:
👉 先跑通真实业务流程,再考虑架构与抽象
需要再次强调的是:
本文中的登录页面(XAML + Code-behind)同样是由 Visual Studio Copilot 生成并整理的实现思路,对应的是一个可直接运行的工程页面。
二、登录页在这个 App 中承担的职责
在这个签到 App 中,登录页并不只是“校验用户名密码”,它实际上承担了多项职责:
- 选择当前租户(Tenant)
- 恢复上一次登录上下文(租户 / 用户名)
- 首次启动时触发引导流程
- 成功登录后,进入真实业务页面
这已经明显不是一个简单 Demo 登录页了。
三、Copilot 生成的登录页面(XAML)
下面是登录页的 UI 布局代码:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SignInMauiApp.LoginPage">
<VerticalStackLayout Padding="30,60" Spacing="20">
<Label Text="签到系统登录" FontSize="24" HorizontalOptions="Center" />
<Image HeightRequest="100" WidthRequest="100" Source="logo.jpg" HorizontalOptions="Center" />
<Picker x:Name="TenantPicker" Title="选择租户" MaximumWidthRequest="600" />
<Entry x:Name="UsernameEntry" Placeholder="用户名" MaximumWidthRequest="600" />
<Entry x:Name="PasswordEntry" Placeholder="密码" IsPassword="True" MaximumWidthRequest="600" />
<Button Text="登录" Clicked="OnLoginClicked" MaximumWidthRequest="600" />
<Button Text="注册新用户" Clicked="OnRegisterClicked" MaximumWidthRequest="600" />
<Label x:Name="ErrorLabel" TextColor="Red" IsVisible="False" MaximumWidthRequest="600" />
</VerticalStackLayout>
</ContentPage>
这个 UI 有几个明显的工程化特征:
- 租户选择是登录的前置条件
- 登录 / 注册入口并列存在
- 所有控件宽度做了限制,适配桌面与移动端
它并不“炫”,但非常实用。
四、Copilot 生成的登录逻辑(Code-behind)
对应的页面逻辑如下:
public partial class LoginPage : ContentPage
{
private readonly IFreeSql? _fsql;
private List<Tenant> _tenants = new();
public LoginPage()
{
InitializeComponent();
_fsql = IPlatformApplication.Current?.Services.GetService<IFreeSql>();
CheckAndShowOnboardingAsync();
LoadTenants();
}
protected override void OnAppearing()
{
base.OnAppearing();
LoadTenants();
}
private async void CheckAndShowOnboardingAsync()
{
var onboardingDone = Preferences.Get("OnboardingDone", false);
if (!onboardingDone)
{
await Navigation.PushModalAsync(new OnboardingPage(_fsql));
}
}
private void LoadTenants()
{
_tenants = _fsql!.Select<Tenant>().ToList();
TenantPicker.ItemsSource = _tenants.Select(t => t.Name).ToList();
int lastTenantId = Preferences.Get("LastTenantId", -1);
int idx = 0;
if (_tenants.Count > 0)
{
if (lastTenantId > 0)
{
idx = _tenants.FindIndex(t => t.Id == lastTenantId);
if (idx < 0) idx = 0;
}
TenantPicker.SelectedIndex = idx;
}
UsernameEntry.Text = Preferences.Get("LastUsername", "");
}
private async void OnLoginClicked(object sender, EventArgs e)
{
ErrorLabel.IsVisible = false;
var username = UsernameEntry.Text?.Trim();
var password = PasswordEntry.Text;
var tenantIdx = TenantPicker.SelectedIndex;
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || tenantIdx < 0)
{
ErrorLabel.Text = "请填写完整信息";
ErrorLabel.IsVisible = true;
return;
}
var tenantId = _tenants[tenantIdx].Id;
var user = _fsql!.Select<User>()
.Where(u => u.Username == username && u.Password == password && u.TenantId == tenantId)
.First();
if (user == null)
{
ErrorLabel.Text = "用户名或密码错误";
ErrorLabel.IsVisible = true;
return;
}
Preferences.Set("LastTenantId", tenantId);
Preferences.Set("LastUsername", username);
await Navigation.PushAsync(new SignInPage(user, _tenants[tenantIdx]));
}
private async void OnRegisterClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new RegisterPage());
}
}
五、几个非常“真实工程”的细节
1️⃣ 登录不是全局的,而是租户内的
.Where(u => u.Username == username && u.Password == password && u.TenantId == tenantId)
这一行明确体现了:
同一个用户名,在不同租户中是完全独立的身份。
2️⃣ 登录体验被认真对待
- 记住上一次租户
- 自动填充用户名
这些并不是必须功能,但它们让 App 更像“真的会被每天使用”。
3️⃣ 引导页不是写在文档里的,而是写在代码里的
CheckAndShowOnboardingAsync();
通过本地标记控制首次引导流程,这是一个非常典型的业务 App 设计。
六、Copilot 在“登录态”问题上的边界
需要特别说明的是:
- Copilot 能写出登录页面
- 但它并不会自动帮你设计完整的登录态生命周期
例如:
- 登录后是否清空返回栈
- 退出登录如何回到初始状态
- 多页面共享用户上下文如何处理
这些问题,已经开始进入应用结构层面。
七、为什么现在依然没有引入认证框架?
和注册页一样,这里依然没有引入:
- Identity
- Token
- Session
原因很简单:
在当前阶段,这个 App 的复杂度还不值得引入这些重量级组件。
而 Copilot 在“简单明确”的业务逻辑下,产出质量反而更高。
八、下一步:初始化数据与租户管理
现在,这个签到 App 已经具备:
- 注册
- 登录
- 多租户隔离
- 基础业务页面
下一步不可避免地会遇到:
系统第一次启动时怎么办?
也就是:
- 初始化租户
- 默认管理员
- 数据兜底策略
九、下一篇预告
下一篇将进入:
第 7 篇:初始化数据与租户管理 —— Copilot 能写 CRUD,但系统规则必须由你决定
从这一篇开始,这个项目将正式进入“可长期维护”的阶段。
关联项目
FreeSql QQ群:4336577
BA & Blazor QQ群:795206915
Maui Blazor 中文社区 QQ群:645660665
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow(包含链接: https://github.com/densen2014 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。
转载声明
本文来自博客园,作者:周创琳 AlexChow,转载请注明原文链接:https://www.cnblogs.com/densen2014/p/19380017
AlexChow
今日头条 | 博客园 | 知乎 | Gitee | GitHub


浙公网安备 33010602011771号