Maui Blazor 中文社区 QQ群:645660665

用 .NET MAUI 10 + VS Copilot 从 0 开发一个签到 App(七)初始化数据与租户管理(全程 Copilot 生成)

用 .NET MAUI 10 + VS Copilot 从 0 开发一个签到 App

第 7 篇:初始化数据与租户管理(全程 Copilot 生成)

本篇为独立完整页面,不与前文步骤混写。

在前几篇中,我们已经完成了:

  • 多租户登录 / 注册
  • 签到与签到历史
  • 基于 FreeSql 的本地数据存储

但一个真实可用的系统,还缺最后一块“地基”:

系统如何完成第一次初始化?租户如何被长期维护?

这一篇,我们只做一件事:

  • 用一个最简单、最真实的方式
  • 完成 初始化数据 + 租户管理能力

并且必须强调:

本篇涉及的页面和逻辑,全部由 Copilot 生成,仅做排版与注释整理。


一、为什么要单独做「租户管理」?

在多租户系统中:

  • 租户不是配置文件
  • 也不是一次性初始化脚本

而是会被真实用户长期维护的数据

在这个 MAUI 签到 App 中,租户至少需要支持:

  • 新建公司
  • 修改公司名称
  • 删除公司(测试 / 演示环境)

与其写复杂初始化逻辑,不如:

直接给管理员一个可视化管理页面


二、TenantManagementPage 页面职责

这一页只做三件事:

  1. 展示现有公司列表
  2. 新增公司
  3. 编辑 / 删除公司

不涉及:

  • 权限系统
  • 用户关联
  • 数据级联

这是一个阶段性基础设施页面


三、页面 UI(XAML,全 AI 生成)

Copilot 生成的 XAML 如下:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SignInMauiApp.TenantManagementPage">
    <VerticalStackLayout Padding="20,30" Spacing="10">
        <Label Text="公司管理" FontSize="22" HorizontalOptions="Center" />
        <Entry x:Name="NewTenantEntry" Placeholder="新公司名称" />
        <Button Text="添加公司" Clicked="OnAddTenantClicked" />
        <CollectionView x:Name="TenantCollectionView" SelectionMode="Single">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <Border Margin="0,5" Padding="10">
                        <HorizontalStackLayout>
                            <Label Text="{Binding Name}" Padding="10" />
                            <Button Text="编辑" Clicked="OnEditTenantClicked" CommandParameter="{Binding .}" />
                            <Button Text="删除" Clicked="OnDeleteTenantClicked" CommandParameter="{Binding .}" />
                        </HorizontalStackLayout>
                    </Border>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>

UI 特点非常明显:

  • 垂直布局
  • CRUD 标准结构
  • 所有事件直连 Code-behind

这是 Copilot 在「管理型页面」场景下最典型、也最实用的输出


四、页面逻辑(Code-behind)

对应的业务代码同样由 Copilot 生成:

public partial class TenantManagementPage : ContentPage
{
    private readonly IFreeSql? _fsql;
    private List<Tenant> _tenants = new();

    public TenantManagementPage()
    {
        InitializeComponent();
        _fsql = IPlatformApplication.Current?.Services.GetService<IFreeSql>();
        LoadTenants();
    }

    private void LoadTenants()
    {
        _tenants = _fsql!.Select<Tenant>().ToList();
        TenantCollectionView.ItemsSource = _tenants;
    }

    private async void OnAddTenantClicked(object sender, EventArgs e)
    {
        var name = NewTenantEntry.Text?.Trim();
        if (string.IsNullOrEmpty(name)) return;

        if (_fsql!.Select<Tenant>().Any(t => t.Name == name))
        {
            await DisplayAlertAsync("提示", "公司已存在", "确定");
            return;
        }

        await _fsql!.Insert(new Tenant { Name = name }).ExecuteAffrowsAsync();
        NewTenantEntry.Text = string.Empty;
        LoadTenants();
    }

    private async void OnEditTenantClicked(object sender, EventArgs e)
    {
        if (sender is Button btn && btn.CommandParameter is Tenant tenant)
        {
            string result = await DisplayPromptAsync("编辑公司", "请输入新名称", initialValue: tenant.Name);
            if (!string.IsNullOrEmpty(result) && result != tenant.Name)
            {
                tenant.Name = result;
                await _fsql!.Update<Tenant>().SetSource(tenant).ExecuteAffrowsAsync();
                LoadTenants();
            }
        }
    }

    private async void OnDeleteTenantClicked(object sender, EventArgs e)
    {
        if (sender is Button btn && btn.CommandParameter is Tenant tenant)
        {
            if (await DisplayAlertAsync("确认", $"确定删除公司:{tenant.Name}?", "删除", "取消"))
            {
                await _fsql!.Delete<Tenant>().Where(t => t.Id == tenant.Id).ExecuteAffrowsAsync();
                LoadTenants();
            }
        }
    }
}

这段代码的特点非常“工程真实”:

  • 逻辑清晰
  • CRUD 直观
  • 没有过度设计

对于一个客户端本地管理页来说,这已经是非常合适的复杂度。


五、初始化数据是如何自然完成的?

你会发现:

  • 没有 InitData
  • 没有 Seed 脚本

因为初始化能力已经被拆解到了:

  • 注册页:创建新租户
  • 租户管理页:维护租户

这是一种非常 MAUI / 客户端化的设计思路

用 UI 行为,完成数据初始化。


六、管理员入口的接入方式

在签到页面中,通过一行逻辑接入:

if (_user.Username == "admin")
{
    ToolbarItems.Add(new ToolbarItem("公司管理", null, async () =>
    {
        await Navigation.PushAsync(new TenantManagementPage());
    }));
}

这是 Copilot 给出的最低成本方案

  • 不引入角色表
  • 不提前设计权限系统

在当前阶段,这是完全合理的工程决策。


七、本篇 Copilot 的价值总结

在「初始化 + 管理型页面」这种场景中:

  • 结构固定
  • 逻辑重复
  • 人工写性价比极低

Copilot 的表现可以总结为一句话:

它生成的不是“完美代码”,而是“马上可用的工程代码”。


八、下一篇预告

功能已经全部完成,下一篇将不再新增页面,而是回头复盘:

第 8 篇:Copilot 在 MAUI 项目中的真实边界与最佳用法

我们会从这 7 篇的真实代码出发,总结:

  • 哪些地方可以 100% 交给 AI
  • 哪些地方必须人工兜底
  • 如果重来一次,我会如何更高效地用 Copilot
posted @ 2025-12-21 22:19  AlexChow  阅读(382)  评论(0)    收藏  举报