用AntDesignBlazor快速开发一个权限系统

发布时间 2023-12-09 09:35:15作者: 竹天笑

写在前面:如果您是一个C#的后台开发人员,又或是C#的WPF开发人,如果想快速开发自己的网站系统,那么选择Blazor技术是太适合你不过了。(在没有Blazor之前,我会推荐Vue),尤其当我看到Ant Desgin Blazor(https://antblazor.com/zh-CN/)全家桶的时候,毫不犹豫就开始了我的愉快之旅。

一、登录界面

直接使用官方例子(https://gitee.com/ant-design-blazor/ant-design-pro-blazor)。然后换个标即可。

二、主界面

主界面同样也使用官方例子,但是为了对接自己的数据结构,稍微改了点

三、构建标准的增删改业务界面

为了避免业务代码的重复,构建两大基类,一个是List的基类,一个是Edit的基类,如下两个界面:

List基类的代码大致如下:

 public class BaseList<TData> : PageBase where TData : IKeyBaseEntity
 {

     [Inject] //API请求
     protected IDataProvider DataProvider { get; set; }
     [Inject] //用户缓存数据
     protected IUserData UserData { get; set; }
     [Inject] //Ant Design Blazor消息框
     protected MessageService MessageService { get; set; }
     [Inject] //Ant Design Blazor对话框
     protected ModalService ModalService { get; set; }

     [Inject] //调用JS脚本
     IJSRuntime JSRuntime { get; set; }
     protected string? Area { get; set; }//区域
     protected string GetDataList { get; set; } = "GetDataList";
     protected string? Condition { get; set; }
     protected string? KeyWord { get; set; }
     protected List<TData> Data { get; set; } = new List<TData>();
     protected TData? SelectedItem { get; set; }
     protected IEnumerable<TData>? SelectedItems { get; set; }
     protected bool NoneSelectedItems { get { return !(SelectedItems?.Count() > 0); } }
     protected Table<TData>? _table;

     protected virtual string GetDataJson()//查询条件拼接
     {         
     }

     protected override async Task GetData()//请求数据
     {
     }

     protected virtual void Edit(TData para)//编辑数据
     {

     }

     protected virtual async Task Delete(string id)//删除数据
     {

     }   

     //获取路由信息,显示界面地址
     protected override async Task OnInitializedAsync()
     {
         if (string.IsNullOrEmpty(IndexUrl))
         {
             IndexUrl = NavigationManager.Uri;
             var uri = new Uri(IndexUrl);
             IndexUrl = uri.LocalPath;
         }
         await base.OnInitializedAsync();
         await GetData();
     }
 }

Edit基类大致如下:

public class BaseEditFormWithOption<TData, Option> : FeedbackComponent<Option>, ILoading
 {
     [Inject]
     protected IDataProvider DataProvider { get; set; }
     [Inject]
     protected IUserData UserData { get; set; }
     [Inject]
     protected MessageService MessageService { get; set; }

  
     protected bool Disabled { get; set; }
     public bool Loading { get; set; }
     protected string Area { get; set; }

     protected TData Data { get; set; }

     protected Form<TData> _form;

     //获取数据
     protected virtual async Task GetDataAsync(Option option)
     {
         
     }

     //保存数据
     protected virtual async Task SaveData(TData para)
     {
        
     }

     //初始化
     protected override async Task OnInitializedAsync()
     {
         var id = this.Options;
         await GetDataAsync(id);

         await base.OnInitializedAsync();
     }

     //按钮操作
     public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
     {
         try
         {
             if (FeedbackRef is ConfirmRef confirmRef)
             {
                 confirmRef.Config.OkButtonProps.Loading = true;
                 await confirmRef.UpdateConfigAsync();
             }
             else if (FeedbackRef is ModalRef modalRef)
             {
                 modalRef.Config.ConfirmLoading = true;
                 await modalRef.UpdateConfigAsync();
             }

             if (_form.Validate())
             {
                 await SaveData(Data);
             }
             else
             {
                 args.Cancel = true;
             }

             await base.OnFeedbackOkAsync(args);
         }
         finally
         {
             if (FeedbackRef is ConfirmRef confirmRef)
             {
                 confirmRef.Config.OkButtonProps.Loading = false;
                 await confirmRef.UpdateConfigAsync();
             }
             else if (FeedbackRef is ModalRef modalRef)
             {
                 modalRef.Config.ConfirmLoading = false;
                 await modalRef.UpdateConfigAsync();
             }
         }
     }
  }

List界面使用的时候代码如下:

@page "/Base_Manage/Base_User/List"

@inherits BaseListWithEdit<Base_UserDTO, EditForm>

<Space Size="@("small")">

    @if (Operator.HasPerm("Base_User.Add"))
    {
        <SpaceItem>
            <Button Type="@ButtonType.Primary" Icon="plus" OnClick="()=>Edit()">新建</Button>
        </SpaceItem>
    }
    @if (Operator.HasPerm("Base_User.Delete"))
    {
        <SpaceItem>
            <Popconfirm Title="确认删除选中项吗?"
                    OnConfirm="()=>Delete()"
                    OnCancel="()=>{}"
                    OkText="确定"
                    CancelText="取消"
                    Disabled=@NoneSelectedItems>
                <Button Type="@ButtonType.Primary" Danger Icon="minus" Disabled=@NoneSelectedItems>删除</Button>
            </Popconfirm>
        </SpaceItem>
    }
    <SpaceItem>
        <Search Placeholder="关键字" EnterButton="true" @bind-Value="@KeyWord" OnSearch="()=>Refresh()" />
    </SpaceItem>
</Space>

<Table @ref="_table"
       TItem="Base_UserDTO"
       DataSource="Data"
       EnableVirtualization
       Loading="Loading"
       @bind-PageSize="Pagination.PageRows"
       @bind-SelectedRows="SelectedItems"
       ScrollX="1400"
       ScrollBarWidth="12px"
       Size="TableSize.Small"
       RowClassName="@(x => x.RowIndex % 2 == 0 ?"gray-2":"")">
    <ChildContent>
        <Selection Key="@(context.Id)" />
        <AntDesign.Column Title="用户名" DataIndex="UserName" TData="string" />
        <AntDesign.Column Title="姓名" DataIndex="RealName" TData="string" />
        <AntDesign.Column Title="性别" DataIndex="SexText" TData="string" />
        <AntDesign.Column Title="出生日期" DataIndex="BirthdayText" TData="string" />
        <AntDesign.Column Title="所属部门" DataIndex="Base_DepartmentName" TData="string" />
        <AntDesign.Column Title="所属角色" DataIndex="RoleNames" TData="string" />
        <ActionColumn Title="Action" Fixed="right">
            <Space Size=@("small")>
                @if (Operator.HasPerm("Base_User.Edit"))
                {
                    <SpaceItem>
                        <Button Type="@ButtonType.Link" Style="padding:0px" OnClick="()=>Edit(context)">Edit</Button>
                    </SpaceItem>
                } 
                @if (Operator.HasPerm("Base_User.Delete"))
                {
                    <SpaceItem>
                        <Popconfirm Title="确认删除吗?"
                                OnConfirm="()=>Delete(context.Id)"
                                OnCancel="()=>{}"
                                OkText="确定"
                                CancelText="取消">
                            <Button Danger Type="@ButtonType.Link" Style="padding:0px">Delete</Button>
                    </Popconfirm>
                </SpaceItem>
                }
            </Space>
        </ActionColumn>
    </ChildContent>
    <PaginationTemplate>
        <div style="float:right;margin-top:10px">
            <Pagination Total="Pagination.Total"
                        ShowTotal="ShowTotal"
                        ShowSizeChanger
                        PageSize="Pagination.PageRows"
                        Current="Pagination.PageIndex"
                        OnChange="PageIndexChanged"
                        OnShowSizeChange="PageSizeChanged" />
        </div>
    </PaginationTemplate>
</Table>

<style>
    .gray-2 {
        #fafafa;
    }
</style>

@code
{
    public List()
    {
        Area = "Base_Manage";
        Condition = "UserName";
        NewTitle = "新建用户";
        EditTitle = "编辑用户";
    }
}

Edit界面使用的时候代码如下:

@inherits BaseEditForm<Base_UserDTO>

<Form @ref="_form" Model="@Data"
      LabelCol="new ColLayoutParam { Span = 8 }"
      WrapperCol="new ColLayoutParam { Span = 16 }">
    <FormItem Label="用户名">
        <Input @bind-Value="@context.UserName" AutoComplete=false/>
    </FormItem>
    <FormItem Label="密码">
        <InputPassword @bind-Value="@context.Password" AutoComplete=false />
    </FormItem>
    <FormItem Label="姓名">
        <Input @bind-Value="@context.RealName" AutoComplete=false />
    </FormItem>
    <FormItem Label="性别">
         <EnumRadioGroup TEnum="Sex" @bind-Value="@context.Sex" Options="GetRadioOptions<Sex>()"></EnumRadioGroup>
    </FormItem>
    <FormItem Label="生日">
        <DatePicker @bind-Value="@context.Birthday" Format="yyyy-MM-dd" />
    </FormItem> 
    <FormItem Label="部门">
        <TreeSelect  
            TItem="TreeModel"
            @bind-Value="@context.DepartmentId" 
         AllowClear
            DataSource="Departments"            
            Placeholder="请选择部门"
            ChildrenExpression="node=>node.DataItem.Children"
            TitleExpression="node=>node.DataItem.Text"
            KeyExpression="node=>node.DataItem.Id"
            IsLeafExpression="node => !(node.DataItem.Children?.Count > 0)"
            TreeDefaultExpandAll />
    </FormItem> 
     <FormItem Label="角色">
        <Select 
            TItem="SelectOption"
            TItemValue="string"
            @bind-Values="@context.RoleIdList"
            AllowClear
            DataSource="Roles"
            Placeholder="请选择角色"
            LabelName="@nameof(SelectOption.Text)"
            ValueName="@nameof(SelectOption.Value)"
            Mode="multiple">
        </Select>
    </FormItem>
</Form>

@code {

    public EditForm()
    {
        Area = "Base_Manage";
    }

    private List<SelectOption> Roles { get; set; }
    private List<TreeModel> Departments { get; set; }

    protected override async Task OnInitializedAsync()
    {
        using (var waitfor = WaitFor.GetWaitFor(this))
        {
            await GetRoles();
            await GetDepartment();
            await base.OnInitializedAsync();
        }
    }

    private async Task GetRoles()
    {
        Roles = await UserData.GetBase_Role();
    }

    private async Task GetDepartment()
    {
        Departments = await UserData.GetBase_DepartmentTree();
    }
    protected override async Task SaveData(Base_UserDTO para)
    {
        await base.SaveData(para);
    }
}

注意代码中的 @if (Operator.HasPerm("...")),可以根据权限判断哪些按钮可以显示。

四、现在越来越多的业务需要流程审批,因此集成了一下流程图界面

五、整个代码结构如下:

另外项目内有后台代码(ASP.NET API),有WPF/Winform/Maui嵌入Blazor的样例。

六、后台代码介绍:

采用ASP.NET Core 7.0的框架,内部实现有jwt验证,DI自动注入,nlog日志,事件总线,SqlSugar,aop拦截,quartz等。

七、相同同框架的WPF框架介绍,使用相同的后台(因为我们的系统使用前后台分离技术,因此客户端可以对接任何技术)

八、写在后面的话

作为一个wpf开发人员去开发BS架构,无论是Ant Desgin Vue,还是Ant Desgin Blazor(https://gitee.com/ant-design-blazor/ant-design-pro-blazor),都是一个不错的选择。

最后上代码地址:

https://gitee.com/akwkevin/AIStudio.Blazor.App

https://github.com/akwkevin/AIStudio.Blazor.App