用AntDesignBlazor快速开发一个权限系统
AIStudio框架汇总及介绍
写在前面:如果您是一个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框架介绍,使用相同的后台(因为我们的系统使用前后台分离技术,因此服务端可以对接任何前端技术)
AIStudio.Wpf.AClient: Wpf客户端框架,AIStudio.Wpf.AClient6.0,全新优化。 (gitee.com)
作为一个wpf开发人员去开发BS架构,无论是Ant Desgin Vue,还是Ant Desgin Blazor(https://gitee.com/ant-design-blazor/ant-design-pro-blazor),都是一个不错的选择。