用 .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,但系统规则必须由你决定
从这一篇开始,这个项目将正式进入“可长期维护”的阶段。
