知识点:
1.导入导出
2.分页功能
3.增删改查
4.批量删除
5.批量编辑(审核)
6.列排序与列搜索
7.顶部搜索实现所有列搜索
8.高级搜索实现多条件搜索
9.顶部与刷新与视图列
10.实现文本类型明细行
11.列的统计
12.隐藏列,时间日期列格式化
13.新窗口打开
14.随机数据
15.自由编辑
16.清空数据
17.模板下载
截图
基础工程
注入FreeSqlDataService
服务,支持全数据导出
更新包
<PackageReference Include="BootstrapBlazor" Version="7.2.3-beta03" /> <PackageReference Include="Densen.FreeSql.Extensions.BootstrapBlazor" Version="7.*" />
Program.cs
添加代码
using Densen.DataAcces.FreeSql; builder.Services.AddSingleton(typeof(FreeSqlDataService<>)); builder.Services.ConfigureJsonLocalizationOptions(op => { // 忽略文化信息丢失日志 op.IgnoreLocalizerMissing = true; });
Index.razor
添加一个 TabItem
Tab 顺便改为懒加载
<Tab IsLazyLoadTabItem="true"> ... <TabItem Text="综合演示"> <ImpExpIII /> </TabItem> </Tab>
添加打印预览 Pages_Host.cshtml
< / body > 前加一句
<script> function printDiv() { window.print(); } </script>
数据实体类 DataSalesChannels.cs
查看代码
using BootstrapBlazor.Components; using DocumentFormat.OpenXml.Wordprocessing; using FreeSql.DataAnnotations; using Magicodes.ExporterAndImporter.Excel; using OfficeOpenXml.Table; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace b14table.Data; [ExcelImporter(IsLabelingError = true)] [ExcelExporter(Name = "导入商品中间表", TableStyle = TableStyles.Light10, AutoFitAllColumn = true)] [AutoGenerateClass(Searchable = true, Filterable = true, Sortable = true, ShowTips = true)] public class SalesChannels { [AutoGenerateColumn(Ignore = true)] [Column(IsIdentity = true)] [DisplayName("序号")] public int ID { get; set; } [AutoGenerateColumn(ComponentType = typeof(ColorPicker), Width = 30)] [DisplayName("级别")] public string? Background { get; set; } [AutoGenerateColumn(FormatString = "yyyy-MM-dd")] [DisplayName("日期")] public DateTime Date { get; set; } [Required(ErrorMessage = "{0}不能为空")] [DisplayName("名称")] public string? Name { get; set; } [DisplayName("项目数量")] public int Projects { get; set; } [DisplayName("交单数量")] public int Orders { get; set; } [DisplayName("结单数量")] public int Checkouts { get; set; } // 编辑界面无法显示小数, 以后再思考 [DisplayName("结单率")] [AutoGenerateColumn(Readonly = true)] public string? CheckoutRates { get => GetCheckoutRates(Checkouts, Orders); set => checkoutRates = value; } string? checkoutRates; [DisplayName("合格数量")] public int Qualifieds { get; set; } [DisplayName("合格率")] [AutoGenerateColumn(Readonly = true)] public string? QualifiedRates { get => GetQualifiedRates(Qualifieds, Checkouts); set => qualifiedRates = value; } string? qualifiedRates; [DisplayName("总价值")] public int Total { get; set; } [DisplayName("应收款")] public int Receivables { get; set; } [DisplayName("已收款")] public int Received { get; set; } [AutoGenerateColumn(FormatString = "HH:mm:ss")] [DisplayName("修改日期")] public DateTime ModifiedDate { get; set; } = DateTime.Now; [AutoGenerateColumn(TextEllipsis = true, Visible = false, ShowTips = true, ComponentType = typeof(Textarea))] [DisplayName("备注")] public string? Remark { get; set; } [AutoGenerateColumn(Visible = false, ComponentType = typeof(BootstrapInput<decimal>), Width = 80)] [DisplayName("Test1")] public decimal Test1 { get; set; } private string GetCheckoutRates(int checkouts, int orders) => orders > 0 ? (checkouts /(double) orders).ToString("P2") : "0%"; private string GetQualifiedRates(int qualifieds, int checkouts) => checkouts > 0 ? (qualifieds / (double)checkouts).ToString("P2") : "0%"; }
页面 PagesImpExpIII.razor
查看代码
@page "/impexpiii" @using b14table.Data @using static Blazor100.Service.ImportExportsService <PageTitle>综合演示</PageTitle> <InputFile OnChange="OnChange" style="max-width:400px" class="form-control" /> <br /> <Table @ref="list1" TItem="SalesChannels" IsPagination="true" IsStriped="true" IsBordered="true" IsDetails="true" AutoGenerateColumns="true" ShowSearch="true" ShowEmpty="true" SearchMode="SearchMode.Top" ShowToolbar="true" ShowExtendButtons="true" DataService="DataService" OnQueryAsync="DataService.QueryAsync" OnSaveAsync="DataService.SaveAsync" OnDeleteAsync="DataService.DeleteAsync" DoubleClickToEdit="@DoubleClickToEdit" IsMultipleSelect="true" ShowLineNo="true" IsExcel="@IsExcel" ShowDetailRow="_ => true" ShowCardView="true" ShowColumnList="true" ShowFooter="true" ScrollingDialogContent="true" EditDialogIsDraggable="true" EditDialogSize="Size.ExtraLarge" EditDialogShowMaximizeButton="true" ShowExportButton OnExportAsync="ExportAsync" PageItemsSource="new int[] {10, 20, 50, 100, 200, 500, 1000 }"> <SearchTemplate> <GroupBox Title="搜索"> <div class="row g-3 form-inline"> <div class="col-12 col-sm-6"> <BootstrapInput @bind-Value="@context.Name" maxlength="50" ShowLabel="true" /> </div> <div class="col-12 col-sm-6"> <BootstrapInput @bind-Value="@context.Date" maxlength="500" ShowLabel="true" /> </div> </div> </GroupBox> </SearchTemplate> <DetailRowTemplate> <div>备注: @context.Remark </div> </DetailRowTemplate> <TableFooter Context="context1"> <TableFooterCell Text="当前页小计:" colspan="4" /> <TableFooterCell Text="总价值" colspan="3" /> <TableFooterCell Aggregate="@Aggregate" Field="@nameof(SalesChannels.Total)" /> <TableFooterCell Text="应收款" colspan="3" /> <TableFooterCell Aggregate="@Aggregate" Field="@nameof(SalesChannels.Receivables)" /> <TableFooterCell Text="已收款" colspan="3" /> <TableFooterCell Aggregate="@Aggregate" Field="@nameof(SalesChannels.Received)" /> </TableFooter> <TableToolbarTemplate> <TableToolbarButton TItem="SalesChannels" Color="Color.Primary" Text="自由编辑" OnClick="@IsExcelToggle" /> <TableToolbarButton TItem="SalesChannels" Color="Color.Warning" Text="随机数据" IsAsync OnClick="@GetDatasAsync" /> <TableToolbarButton TItem="SalesChannels" Color="Color.Secondary" Text="导入" IsAsync OnClick="@ImportExcel" /> <TableToolbarButton TItem="SalesChannels" Color="Color.Danger" Text="清空" IsAsync OnClick="EmptyAll" /> <TableToolbarButton TItem="SalesChannels" Color="Color.Success" Text="模板" IsAsync OnClick="Export模板Async" /> <TableToolbarButton TItem="SalesChannels" Color="Color.Success" Text="打印" IsAsync OnClickCallback="@PrintPreview" /> <TableToolbarButton TItem="SalesChannels" Color="Color.Secondary" Text="新窗口打开" IsAsync OnClick="@新窗口打开" /> <TableToolbarButton TItem="SalesChannels" Color="Color.Secondary" Text="批量审批" IsAsync OnClickCallback="@批量审批" /> </TableToolbarTemplate> <ExportButtonDropdownTemplate> <h6 class="dropdown-header">当前页数据</h6> <div class="dropdown-item" @onclick="_=>ExportExcelAsync(list1.Rows)"> <i class="fas fa-file-excel"></i> <span>Excel</span> </div> <div class="dropdown-item" @onclick="_=>ExportWordAsync(list1.Rows)"> <i class="fas fa-file-word"></i> <span>Word</span> </div> <div class="dropdown-item" @onclick="_=>ExportHtmlAsync(list1.Rows)"> <i class="fa-brands fa-html5"></i> <span>Html</span> </div> <div class="dropdown-item" @onclick="_=>ExportPDFAsync(list1.Rows)"> <i class="fas fa-file-pdf"></i> <span>PDF</span> </div> <div class="dropdown-divider"></div> <h6 class="dropdown-header">全部数据</h6> <div class="dropdown-item" @onclick="_=>ExportExcelAsync(DataService.GetAllItems())"> <i class="fas fa-file-excel"></i> <span>Excel</span> </div> <div class="dropdown-item" @onclick="_=>ExportWordAsync(DataService.GetAllItems())"> <i class="fas fa-file-word"></i> <span>Word</span> </div> <div class="dropdown-item" @onclick="_=>ExportHtmlAsync(DataService.GetAllItems())"> <i class="fa-brands fa-html5"></i> <span>Html</span> </div> <div class="dropdown-item" @onclick="_=>ExportPDFAsync(DataService.GetAllItems())"> <i class="fas fa-file-pdf"></i> <span>PDF</span> </div> </ExportButtonDropdownTemplate> </Table>
页面代码 PagesImpExpIII.razor
查看代码
using AmeBlazor.Components; using b14table.Data; using Blazor100.Service; using BootstrapBlazor.Components; using Densen.DataAcces.FreeSql; using DocumentFormat.OpenXml.Spreadsheet; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.JSInterop; using System.Diagnostics.CodeAnalysis; using static Blazor100.Service.ImportExportsService; namespace b14table.Pages { public partial class ImpExpIII { [Inject] IWebHostEnvironment? HostEnvironment { get; set; } [Inject] [NotNull] NavigationManager? NavigationManager { get; set; } [Inject] [NotNull] ImportExportsService? ImportExportsService { get; set; } [Inject] [NotNull] ToastService? ToastService { get; set; } [Inject] [NotNull] FreeSqlDataService<SalesChannels>? DataService { get; set; } [NotNull] Table<SalesChannels>? list1 { get; set; } [Parameter] public int Footercolspan1 { get; set; } = 3; [Parameter] public int Footercolspan2 { get; set; } = 2; [Parameter] public int Footercolspan3 { get; set; } [Parameter] public int FootercolspanTotal { get; set; } = 2; [Parameter] public string? FooterText { get; set; } = "合计:"; [Parameter] public string? FooterText2 { get; set; } [Parameter] public string? FooterText3 { get; set; } [Parameter] public string? FooterTotal { get; set; } /// <summary> /// 获得/设置 IJSRuntime 实例 /// </summary> [Inject] [NotNull] protected IJSRuntime? JsRuntime { get; set; } [Parameter] public string? 新窗口打开Url { get; set; } = "https://localhost:7292/"; // 由于使用了FreeSql ORM 数据服务,可以直接取对象 [Inject] [NotNull] IFreeSql? fsql { get; set; } [Inject] ToastService? toastService { get; set; } [Inject] SwalService? SwalService { get; set; } public bool IsExcel { get; set; } public bool DoubleClickToEdit { get; set; } = true; protected string UploadPath = ""; protected string? uploadstatus; long maxFileSize = 1024 * 1024 * 15; string? tempfilename; private AggregateType Aggregate { get; set; } protected async Task GetDatasAsync() { var datas = GetDemoDatas(); await fsql.Insert<SalesChannels>().AppendData(datas).ExecuteAffrowsAsync(); await list1!.QueryAsync(); } protected override async void OnAfterRender(bool firstRender) { if (firstRender) { UploadPath = Path.Combine(HostEnvironment!.WebRootPath, "uploads"); if (!Directory.Exists(UploadPath)) Directory.CreateDirectory(UploadPath); await list1!.QueryAsync(); } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { //懒的人,直接初始化一些数据用用 var res = fsql.Select<SalesChannels>().Count(); if (res == 0) { var datas = GetDemoDatas(); await fsql.Insert<SalesChannels>().AppendData(datas).ExecuteAffrowsAsync(); await list1!.QueryAsync(); } } } public List<SalesChannels> GetDemoDatas() { var list = new List<SalesChannels>(); for (int i = 0; i < 100; i++) { try { var total = Random.Shared.Next(100, 3000); list.Add(new SalesChannels() { ID = i, Name = "渠道" + i, Date = DateTime.Now, Projects = Random.Shared.Next(10, 55), Orders = Random.Shared.Next(3, 10), Qualifieds = i, Total = total, Receivables = total - i, Received = i, Remark= $"{i} 明细行内嵌套另外一个 Table 组件,由于每行都要关联子表数据,出于性能的考虑,此功能采用 懒加载 模式,即点击展开按钮后,再对嵌套 Table 进行数据填充,通过 ShowDetailRow 回调委托可以控制每一行是否显示明细行,本例中通过 Complete 属性来控制是否显示明细行,可通过翻页来测试本功能" }); } catch (Exception e) { System.Console.WriteLine(e.Message); } } return list; } private Task IsExcelToggle() { IsExcel = !IsExcel; DoubleClickToEdit = !IsExcel; StateHasChanged(); return Task.CompletedTask; } public async Task<bool> Export模板Async() { await Export(); return true; } private async Task<bool> ExportExcelAsync(IEnumerable<SalesChannels> items) => await ExportAutoAsync(items, ExportType.Excel); private async Task<bool> ExportPDFAsync(IEnumerable<SalesChannels> items) => await ExportAutoAsync(items, ExportType.Pdf); private async Task<bool> ExportWordAsync(IEnumerable<SalesChannels> items) => await ExportAutoAsync(items, ExportType.Word); private async Task<bool> ExportHtmlAsync(IEnumerable<SalesChannels> items) => await ExportAutoAsync(items, ExportType.Html); private async Task<bool> ExportAutoAsync(IEnumerable<SalesChannels> items, ExportType exportType = ExportType.Excel) { if (items == null || !items.Any()) { await ToastService.Error("提示", "无数据可导出"); return false; } var option = new ToastOption() { Category = ToastCategory.Information, Title = "提示", Content = $"导出正在执行,请稍等片刻...", IsAutoHide = false }; // 弹出 Toast await ToastService.Show(option); await Task.Delay(100); // 开启后台进程进行数据处理 await Export(items?.ToList(), exportType); // 关闭 option 相关联的弹窗 option.Close(); // 弹窗告知下载完毕 await ToastService.Show(new ToastOption() { Category = ToastCategory.Success, Title = "提示", Content = $"导出成功,请检查数据", IsAutoHide = false }); return true; } private async Task Export(List<SalesChannels>? items = null, ExportType exportType = ExportType.Excel) { try { if (items == null || !items.Any()) { ToastService?.Error($"导出", $"{exportType}出错,无数据可导出"); return; } var fileName = items == null ? "模板" : typeof(SalesChannels).Name; var fullName = Path.Combine(UploadPath, fileName); fullName = await ImportExportsService.Export(fullName, items, exportType); fileName = (new System.IO.FileInfo(fullName)).Name; ToastService?.Success("提示", fileName + "已生成"); //下载后清除文件 NavigationManager.NavigateTo($"uploads/{fileName}", true); _ = Task.Run(() => { Thread.Sleep(50000); System.IO.File.Delete(fullName); }); } catch (Exception e) { ToastService?.Error($"导出", $"{exportType}出错,请检查. {e.Message}"); } } public async Task<bool> EmptyAll() { fsql.Delete<SalesChannels>().Where(a => 1 == 1).ExecuteAffrows(); await ToastService!.Show(new ToastOption() { Category = ToastCategory.Success, Title = "提示", Content = "已清空数据", }); await list1!.QueryAsync(); return true; } private async Task ImportExcel() { if (string.IsNullOrEmpty(tempfilename)) { ToastService?.Error("提示", "请正确选择文件上传"); return; } var option = new ToastOption() { Category = ToastCategory.Information, Title = "提示", Content = "导入文件中,请稍等片刻...", IsAutoHide = false }; // 弹出 Toast await ToastService!.Show(option); await Task.Delay(100); // 开启后台进程进行数据处理 var isSuccess = await MockImportExcel(); // 关闭 option 相关联的弹窗 option.Close(); // 弹窗告知下载完毕 await ToastService.Show(new ToastOption() { Category = isSuccess ? ToastCategory.Success : ToastCategory.Error, Title = "提示", Content = isSuccess ? "操作成功,请检查数据" : "出现错误,请重试导入或者上传", IsAutoHide = false }); await list1!.QueryAsync(); } private async Task<bool> MockImportExcel() { var items_temp = await ImportExportsService!.ImportFormExcel<SalesChannels>(tempfilename!); if (items_temp.items == null) { ToastService?.Error("提示", "文件导入失败: " + items_temp.error); return false; } //items = SmartCombine(items_temp, items).ToList(); 新数据和老数据合并处理,略100字 await fsql.Insert<SalesChannels>().AppendData(items_temp!.items.ToList()).ExecuteAffrowsAsync(); return true; } protected async Task OnChange(InputFileChangeEventArgs e) { if (e.File == null) return; tempfilename = Path.Combine(UploadPath, e.File.Name); await using FileStream fs = new(tempfilename, FileMode.Create); using var stream = e.File.OpenReadStream(maxFileSize); await stream.CopyToAsync(fs); //正式工程此处是回调,简化版必须InvokeAsync一下,自由发挥 _ = Task.Run(async () => await InvokeAsync(async () => await ImportExcel())); } /// <summary> /// 导出数据方法 /// </summary> /// <param name="Items"></param> /// <param name="opt"></param> /// <returns></returns> protected async Task<bool> ExportAsync(IEnumerable<SalesChannels> Items, QueryPageOptions opt) { var ret = await ExportExcelAsync(Items); return ret; } public Task PrintPreview(IEnumerable<SalesChannels> item) { //实际工程自己完善js打印 JsRuntime.InvokeVoidAsync("printDiv"); return Task.CompletedTask; } private Task 新窗口打开() { if (string.IsNullOrEmpty(新窗口打开Url)) { ToastService?.Error("提示", "Url为空!"); return Task.CompletedTask; } JsRuntime.NavigateToNewTab(新窗口打开Url); return Task.CompletedTask; } public async Task 批量审批(IEnumerable<SalesChannels> items) { items.ToList().ForEach(a => { a.Checkouts = a.Orders; a.Receivables = 0; a.Received = a.Total; a.ModifiedDate = DateTime.Now; }); var res = await fsql.Update<SalesChannels>().SetSource(items).ExecuteAffrowsAsync(); await SwalService!.Show(new SwalOption() { Title = res == 0 ? "提示: 操作失败" : "提示: 操作成功" }); if (res != 0) await list1!.QueryAsync(); } } }
预览
源代码
https://gitee.com/densen2014/Blazor100/tree/master/b04table
https://github.com/densen2014/Blazor100/tree/master/b04table
项目地址
https://gitee.com/LongbowEnterprise/BootstrapBlazor