前言

大家好,我是开源项目 MASA Blazor 主要开发者之一,如果你还不了解MASA Blazor,可以访问我们的 官网 和博客 《初识MASA Blazor》 一探究竟。简单来说,MASA Blazor 是一个基于 Material Design 设计语言的 Blazor 组件库,dotNET开发者只需或者甚至不需要懂得 javascript 就能开发一个企业级中后台系统。

我这次分享的主题是《使用MASA Blazor开发一个标准的查询表格页》,我会先从创建项目开始手撸一个没有任何技巧的查询表格页,然后我会分享一些技巧和封装的组件,实现快速开发。

手撸查询表格页

创建应用程序

关于如何安装MASA Blazor模板,请移步 MASA.Blazor快速入门

  1. 首先通过MASA Blazor模板默认的Server项目,项目命名为 MasaBlazorStandardTablePage

    dotnet new --install MASA.Template
    dotnet new masab -o MasaBlazorStandardTablePage
  2. 通过CLI运行应用程序,或直接通过vs启动项目。

    cd MasaBlazorStandardTablePage
    dotnet run
  3. 启动成功后切换到 Fetch data 页面,此页面展示了一个简单的使用了 MDataTable 的表格。

支持单个查询条件和搜索

让我们从最简单的单个条件查询开始。

将随机数据替换成模拟数据

  1. 修改 WeatherForecastService,将随机数据替换成死数据以便支持查询功能。下面的代码更新了数据来源和 GetForecastAsync 查询方法。

        public class WeatherForecastService
    {
    private readonly List<WeatherForecast> _data = new()
    {
    new() { Date = DateTime.Now.AddDays(-1), TemperatureC = 23, Summary = "Freezing" },
    new() { Date = DateTime.Now.AddDays(-1), TemperatureC = -10, Summary = "Bracing" },
    new() { Date = DateTime.Now.AddDays(-1), TemperatureC = 37, Summary = "Chilly" },
    new() { Date = DateTime.Now.AddDays(-2), TemperatureC = 29, Summary = "Cool" },
    new() { Date = DateTime.Now.AddDays(-3), TemperatureC = 11, Summary = "Mild" },
    new() { Date = DateTime.Now.AddDays(-4), TemperatureC = 35, Summary = "Warm" },
    new() { Date = DateTime.Now.AddDays(-5), TemperatureC = 41, Summary = "Balmy" },
    new() { Date = DateTime.Now.AddDays(-5), TemperatureC = -13, Summary = "Hot" },
    new() { Date = DateTime.Now.AddDays(-6), TemperatureC = 23, Summary = "Sweltering" },
    new() { Date = DateTime.Now.AddDays(-7), TemperatureC = 2, Summary = "Scorching" },
    }; public Task<WeatherForecast[]> GetForecastAsync()
    {
    IEnumerable<WeatherForecast> res = _data.AsQueryable(); return Task.FromResult(res.ToArray());
    }
    }
  2. 同时修改 FetchData.razor,因为 WeatherForecastService.GetForecastAsync() 删除了 startDate 入参。

    protected override async Task OnInitializedAsync()
    {
    await Task.Delay(1000); // 模拟真实环境,触发Loading效果
    forecasts = await ForecastService.GetForecastAsync(); // here
    }

添加查询输入框和搜索按钮

  1. FetchData.razor 页面中的 <p> 标签下添加以下代码

    <MRow Class="pb-3">
    <MCol Cols="12">
    <MTextField @bind-Value="summary"
    Dense
    HideDetails="@("auto")"
    Label="Summary"
    Outlined>
    </MTextField>
    </MCol>
    <MCol Cols="12" Class="d-flex py-0 pb-3">
    <MSpacer></MSpacer>
    <MButton Color="primary" OnClick="OnSearch">搜索</MButton>
    </MCol>
    </MRow> @code {
    private string summary; private async Task OnSearch()
    {
    forecasts = await ForecastService.GetForecastAsync(summary);
    }
    }
    • Line 3,17

      定义了一个 string 类型的名为 summary 的变量,双向绑定给了 MTextField 组件。MTextFiled 除了 @bind-Value 属性用于设置双向绑定,其他属性的含义请阅读 文档

    • Line 12

      定义了一个搜索按钮,用于触发查询。

  2. 修改 WeatherForecastService.GetForecastAsync 方法,增加 summary 入参,并支持查询。

    public Task<WeatherForecast[]> GetForecastAsync(string? summary = null)
    {
    IEnumerable<WeatherForecast> res = _data.AsQueryable(); if (!string.IsNullOrEmpty(summary))
    {
    res = res.Where(item => item.Summary.Contains(summary));
    } return Task.FromResult(res.ToArray());
    }

支持多个查询条件和重置

现在让我们再添加一个高温预警的选择框来查询不同高温预警状态的数据。

更新 WeatherForecastService 以支持根据高温预警筛选数据

public Task<WeatherForecast[]> GetForecastAsync(string? summary = null, WarningSigns? warningSigns = null)
{
IEnumerable<WeatherForecast> res = _data.AsQueryable(); if (!string.IsNullOrEmpty(summary))
{
res = res.Where(item => item.Summary.Contains(summary));
} if (warningSigns.HasValue)
{
res = warningSigns switch
{
WarningSigns.Yellow => res.Where(item => item.TemperatureC >= 35 && item.TemperatureC < 37),
WarningSigns.Orange => res.Where(item => item.TemperatureC >= 37 && item.TemperatureC < 39),
WarningSigns.Red => res.Where(item => item.TemperatureC >= 39),
_ => res
};
} return Task.FromResult(res.ToArray());
}

增加高温预警选择框

  1. Data 目录下添加名为 WarningSigns 的枚举。

    public enum WarningSigns
    {
    [Description("高温黄色预警 35℃")]
    Yellow = 1,
    [Description("高温橙色预警 37℃")]
    Orange,
    [Description("高温红色预警 39℃")]
    Red
    }
  2. 引入 Masa.Utils.Enums 包,此包提供的 GetEnumObjectList 方法能轻松的将枚举的 Description 和枚举值用于 MSelect 组件的 Items

    dotnet add package Masa.Utils.Enums
  3. 更新 FetchData.razor

    <MRow Class="pb-3">
    <MCol Cols="12" Sm="6">
    <MTextField @bind-Value="@summary"
    Label="Summary"
    Dense
    HideDetails="@("auto")"
    Outlined>
    </MTextField>
    </MCol>
    <MCol Cols="12" Sm="6">
    <MSelect @bind-Value="warningSigns"
    Items="@(Enum<WarningSigns>.GetEnumObjectList<WarningSigns>())"
    ItemText="item => item.Name"
    ItemValue="item => item.Value"
    TValue="WarningSigns?"
    TItem="EnumObject<WarningSigns>"
    TItemValue="WarningSigns"
    Label="高温警告"
    Clearable
    Dense
    HideDetails="@("auto")"
    Outlined>
    </MSelect>
    </MCol>
    <MCol Cols="12" Class="d-flex py-0 pb-3">
    <MSpacer></MSpacer>
    <MButton Class="mr-2" OnClick="OnReset">重置</MButton>
    <MButton Color="primary" OnClick="OnSearch">搜索</MButton>
    </MCol>
    </MRow> @code {
    private WarningSigns? warningSigns; private Task OnReset()
    {
    summary = null;
    warningSigns = null;
    return OnSearch();
    } private async Task OnSearch()
    {
    forecasts = await ForecastService.GetForecastAsync(summary, warningSigns);
    }
    }
    • Line 2,10

    通过设置 Sm="6" 可以让屏幕尺寸大于768px时一行占两个 MCol ,实现 MTextFieldMSelect 并排显示。

    • Line 11-23,33,44

    第33行定义 warningSigns 变量用于接收 MSelect 选中的值,当然也可以通过设置值更新 MSelect 选中的值,只要设置了 @bind-Value 双向绑定就行,就像第11行那样。第12行使用了 Masa.Utils.Enums 提供的方法,返回了一个包含Name(Description)和Value(枚举值)的列表,赋值给了 MSelect.Items 。第44行将 warningSigns 的传给查询接口。

    • Line 27,35-40

    此处定义了一个重置按钮,用于清空所有查询输入框的内容并刷新表格。

支持键入回车或选择后触发查询

后来测试小姐姐说你这太难用了,回车不能触发搜索,选择完也不能触发搜索。好吧好吧,我们现在加上。

键入回车后触发

原理即捕捉 OnKeyDown 事件是否点击了 Enter 键。

<MTextField @bind-Value="@summary"
OnKeyDown="HandleOnKeyDown"
Label="Summary"
Dense
HideDetails="@("auto")"
Outlined>
</MTextField> @code {
private async Task HandleOnKeyDown(KeyboardEventArgs args)
{
if (args.Code == "Enter")
{
// 等待156毫秒,预防输入的值在更新到变量之前按下Enter键
await Task.Delay(156); await OnSearch();
}
}
}
  • Line 2

    HandleOnKeyDown 绑定到 MTextFieldOnKeyDown 事件。

  • Line 10-17

    通过判断 KeyboardEventArgsCode 值是否为 Enter 来触发搜索。第14行等待156毫秒是为了等待 summary 的值已经是输入过后的值。

选择后触发查询

<MSelect @bind-Value="warningSigns"
Items="@(Enum<WarningSigns>.GetEnumObjectList<WarningSigns>())"
ItemText="item => item.Name"
ItemValue="item => item.Value"
TValue="WarningSigns?"
TItem="EnumObject<WarningSigns>"
TItemValue="WarningSigns"
Label="高温警告"
OnSelectedItemUpdate="OnSearch"
Clearable
Dense
HideDetails="@("auto")"
Outlined>
</MSelect>
  • Line 9

    当选择项更新时(OnSelectedItemUpdate)直接调用 OnSearch 方法,触发查询。此处不用像上面处理 OnKeyDown 那样等待156毫秒,因为 OnSelectedItemUpdate 是在 warningSigns 更新后触发的。

点击清空图标触发查询

很简单,只要给 MTextFieldMSelect 组件添加以下属性:

Clearable
OnClearClick="OnSearch"

加点Loading动画可好?

好!

<MButton Color="primary"
Loading="searching"
OnClick="HandleOnSearch">
搜索
</MButton>
... <MDataTable Headers="_headers" Items="forecasts"
Loading="loading"
ItemsPerPage="5" Class="elevation-1">
... @code {
private bool loading;
private bool searching; private async Task HandleOnSearch()
{
searching = true; await OnSearch(); searching = false;
} private async Task OnSearch()
{
loading = true; await Task.Delay(1000);
forecasts = await ForecastService.GetForecastAsync(summary, warningSigns); loading = false;
}
}
  • Line 2-3,15,17-24

    新增 searching 变量用于控制搜索按钮的 Loading 状态,同时新增了 HandleOnSearch 代替原来的 OnSearch 是为了单独控制点击搜索按钮的动画。

  • Line 9,14,28,33

    新增 loading 变量用于控制 MDataTableLoading 状态。OnSearch 方法块中在接口请求前后设置 loading 的值。

表的行操作和自定义列样式

因为篇幅限制,我就不一一把代码贴出来了,具体代码请查阅 源码 接下来我将针对Table写一些常见的代码,如行操作和自定义列样式。

封装组件和技巧

我本应该用这节分享的内容将上面的例子重构的过程写出来,但感觉会使得本文太冗长。重构后的代码我也会上传到 Github 上。

封装组件

试想一下,当你被分配到好几个模块,每个模块都有至少一个查询表格页,你会如何开发?你大概会说复制最合适的代码文件,然后重命名文件名,重命名相应的变量,修修改改就完行了。当然这是一个方法,但不优雅。那优雅的方式是什么,是封装。我有段时间在全职开发 MASA.Blazor 组件库,后面因为业务需求分配到了IoT项目帮助Blazor后台系统的研发和 MASA.Blazor 的实践。在开发IoT项目时,经常会看见相同的代码分布在相同的类中,我试着优化重构这些代码,并从查询表格页中抽离封装了以下几个组件:

  • Filters:接收OnSearch参数代理查询,通过context提供onEnteronSearch方法供单个查询组件使用。
  • PageHeader:一个标准的页头,包括了标题、副标题、搜索按钮,并提供Filters组件的能力。
  • Actions:提供一组操作按钮,默认展示前两个,后面的按钮会移动到MMenu中显示。
  • BlockText:将相同类型的两个数据并列显示。
  • ColorChip:提供有限的颜色列表生成带浅色字体的MChip
  • CopyableText:在文本后提供可以复制内容的图标按钮。
  • DateTimePicker:提高带时分秒选择器的弹出层时间选择器。
  • EllipsisText:根据父级盒子的宽度自动截断文本。
  • GenericColumnRender:渲染DateTime、枚举、bool和其他类型值。可以用于MDataTableItemColContentDefinitionsDetailContent

PageHeader组件作为 MASA.Blazor 预置组件的一部分已经发布,其他提及的组件还没有并入 MASA.Blazor 主库。如果你想要使用或参考,可以访问 MASA.Blazor.Experimental.Components。关于预置组件和实验性组件的详细介绍和使用的文章,后面会由其他同事编写和发布,请大家带多多关注!

MASA.Blazor.Experimental.Components 是一个实验性组件库,这意味着该库的API和功能可能会被重新设计。不过随着实验性组件的功能不断完善和稳定,会随着 MASA.Blazor 版本的更新而并入主库。

技巧

善用基类

Blazor的组件其实也是一个类,它默认继承自 ComponentBase 并提供了许多虚拟方法,我们可以重写它们来影响应用程序的行为。而这些方法通过继承机制给所有Blazor组件使用。

在实际开发中,我会发现几乎每个页面都会注入 NavigationManagerIJsRunTime 和其他可能存在的业务服务,或者会使用某些共同使用的组件,那我们可以在继承 ComponentBase 的基础上再写一个已经使用了这些服务和组件的基类。

按架构可以创建专门给 @page 组件用的 PageComponentBase 和单纯封装功能的 PureComponentBase

按业务分类就得看情况了,因为业务更加具体,基类里通常会有注入 HttpClient 或者同类型业务服务,以及任何共同使用的代码。

SetParametersAsync

SetParametersAsync sets parameters supplied by the component's parent in the render tree or from route parameters.

只需知道每当父级呈现时,都会执行此方法。这意味着它是指定默认参数值的正确位置。

拿前面的例子来说,在使用 MTextFieldMSelect 时都会设置以下代码来维持相同的外观和行为:

Clearable
Dense
HideDetails="@("auto")"
Outlined

那么与其每次都要写一遍,不如利用 SetParametersAsync 的特性把这些默认参数提前设置:

public class DefaultTextField<TValue> : MTextField<TValue>
{
public override async Task SetParametersAsync(ParameterView parameters)
{
Clearable = true;
Dense = true;
HideDetails = "auto";
Outlined = true; await base.SetParametersAsync(parameters);
}
} public class DefaultSelect<TItem, TItemValue, TValue> : MSelect<TItem, TItemValue, TValue>
{
public override async Task SetParametersAsync(ParameterView parameters)
{
Clearable = true;
Dense = true;
HideDetails = "auto";
Outlined = true; await base.SetParametersAsync(parameters);
}
}
<DefaultTextField @bind-Value="@summary"
OnKeyDown="@context.onEnter"
OnClearClick="@context.onSearch"
Label="Summary">
</DefaultTextField> <DefaultSelect @bind-Value="warningSigns"
Items="@(Enum<WarningSigns>.GetEnumObjectList<WarningSigns>())"
ItemText="item => item.Name"
ItemValue="item => item.Value"
TValue="WarningSigns?"
TItem="EnumObject<WarningSigns>"
TItemValue="WarningSigns"
Label="高温警告"
OnSelectedItemUpdate="@context.onSearch"
OnClearClick="@context.onSearch">
</DefaultSelect>

未来的计划

未来我们团队将继续优化各个组件的性能,完成缺失的组件,解决BUG问题,完善文档等。另外,我们也计划出Blazor相关的教程和分享文章,敬请期待。

感谢阅读!

资源

开源地址

MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks

MASA.Contrib:https://github.com/masastack/MASA.Contrib

MASA.Utils:https://github.com/masastack/MASA.Utils

MASA.EShop:https://github.com/masalabs/MASA.EShop

MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

使用MASA Blazor开发一个标准的查询表格页的更多相关文章

  1. MASA Blazor入门这一篇就够了

    1.什么是Blazor? 有什么优势? ASP.NET Core Blazor 简介 Blazor 是一个使用 Blazor 生成交互式客户端 Web UI 的框架: 使用 C# 代替 JavaScr ...

  2. 体验了一把最近很火的开源项目-MASA Blazor

    前言 很惭愧直到去年底才接触到Blazor.那什么是Blazor呢?相信大家都看过官方文档的详细说明,另外MASA团队也有不错的说明介绍 .用官方的话说Blazor是一个交互式客户端Web UI的框架 ...

  3. 初识MASA Blazor

    MASA Blazor是一个Blazor的UI组件库.就像大家写前端熟知的Bootstrap, Ant Design一样. MASA Blazor官网地址:https://blazor.masasta ...

  4. 在MAUI中使用Masa Blazor

    Masa Blazor是什么 在此之前我们已经介绍过什么是Masa Blazor,以及如何使用Masa Balzor,如果还有不了解Masa Blazor的同学可以看我上篇文章[初识Masa Blaz ...

  5. 基于c++11新标准开发一个支持多线程高并发的网络库

    背景 新的c++11标准出后,c++语法得到了非常多的扩展,比起以往不论什么时候都要灵活和高效,提高了程序编码的效率,为软件开发者节省了不少的时间. 之前我也写过基于ACE的网络server框架,但A ...

  6. Django实战总结 - 快速开发一个数据库查询工具

    一.简介 Django 是一个开放源代码的 Web 应用框架,由 Python 写成. Django 只要很少的代码就可以轻松地完成一个正式网站所需要的大部分内容,并进一步开发出全功能的 Web 服务 ...

  7. django学习-11.开发一个简单的醉得意菜单和人均支付金额查询页面

    1.前言 刚好最近跟技术部门的[产品人员+UI人员+测试人员],组成了一桌可以去公司楼下醉得意餐厅吃饭的小team. 所以为了实现这些主要点餐功能: 提高每天中午点餐效率,把点餐时间由20分钟优化为1 ...

  8. Masa Blazor in Blazor Day

    2022年第一场Blazor中文社区的开发者分享活动,我们的团队也全程参与其中,在议程中,也分享了我们团队的Blazor 管理后台模板,针对于Blazor,先科普一波,避免有些朋友不了解,Blazor ...

  9. Masa Blazor自定义组件封装

    前言 实际项目中总能遇到一个"组件"不是基础组件但是又会频繁复用的情况,在开发MASA Auth时也封装了几个组件.既有简单定义CSS样式和界面封装的组件(GroupBox),也有 ...

随机推荐

  1. python程序语法元素分析

    #TemConvert.py TempStr = input("请输入带有符号的温度值:") if TempStr[-1] in ['F', 'f']: C = (eval(Tem ...

  2. php截取字符串,避免乱码

    转载请注明来源:https://www.cnblogs.com/hookjc/ 1. 截取GB2312中文字符串 <?php//截取中文字符串 function mysubstr($str, $ ...

  3. Block基本概念

    1.什么是Block Block是iOS中一种比较特殊的数据类型 Block是苹果官方特别推荐使用的数据类型, 应用场景比较广泛 动画 多线程 集合遍历 网络请求回调 Block的作用 用来保存某一段 ...

  4. 关于LVS的问题总结

    关于LVS的问题总结 目录 关于LVS的问题总结 1. LVS工作模式及区别 2. LVS调度算法 3. LVS调度器你的常用算法(均衡策略) (1)固定调度算法:rr.wrr.dh.sh (2)动态 ...

  5. logback1.3.x配置详解与实践

    前提 当前(2022-02前后)日志框架logback的最新版本1.3.0已经更新到1.3.0-alpha14版本,此版本为非stable版本,相对于最新稳定版1.2.10来说,虽然slf4j-api ...

  6. 使用 Sa-Token 解决 WebSocket 握手身份认证

    前言 相比于 Http 的单项通信方式,WebSocket 可以从服务器向浏览器主动推送消息,这一特性可以帮助我们完成诸如 订单消息推送.IM实时聊天 等一些特定业务. 然而 WebSocket 本身 ...

  7. 将自己的web应用发布到Tomcat

    方法一:(用这个方法最好先把ROOT文件夹备份好,不建议使用) 1,打开tomcat 的目录,在webapps 的目录下, 把命名为ROOT 的文件夹删掉, 然后把自己的war 包更名为 ROOT.w ...

  8. 面试题之java缓存总结,从单机缓存到分布式缓存架构

    1.缓存定义 高速数据存储层,提高程序性能 2.为什么要用缓存(读多写少,高并发) 1.提高读取吞吐量 2.提升应用程序性能 3.降低数据库成本 4.减少后端负载 5.消除数据库热点 6.可预测的性能 ...

  9. SpringBoot一览

    spring-boot入门 了解SpringBoot 为什么学习SpringBoot java一直被人诟病的一点就是臃肿.麻烦.当我们还在辛苦的搭建项目时,可能Python程序员已经把功能写好了,究其 ...

  10. Note/Solution -「洛谷 P5158」「模板」多项式快速插值

    \(\mathcal{Description}\)   Link.   给定 \(n\) 个点 \((x_i,y_i)\),求一个不超过 \(n-1\) 次的多项式 \(f(x)\),使得 \(f(x ...