4

[硬核] Bootstrap Blazor Table 综合演示例子 - AlexChow

 1 year ago
source link: https://www.cnblogs.com/densen2014/p/17053020.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1.导入导出
2.分页功能
3.增删改查
4.批量删除
5.批量编辑(审核)
6.列排序与列搜索
7.顶部搜索实现所有列搜索
8.高级搜索实现多条件搜索
9.顶部与刷新与视图列
10.实现文本类型明细行
11.列的统计
12.隐藏列,时间日期列格式化
13.新窗口打开
14.随机数据
15.自由编辑
16.清空数据
17.模板下载

1980213-20230115044503080-250240265.jpg
1980213-20230115044450590-1369455554.jpg
1980213-20230115044458709-665667683.jpg

表格组件导出 Excel Word Html Pdf

注入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>

数据实体类 Data\SalesChannels.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%";


}

页面 Pages\ImpExpIII.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> 

页面代码 Pages\ImpExpIII.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

项目Wiki

https://gitee.com/LongbowEnterprise/BootstrapBlazor/wikis/QuickStart/[硬核] Table 综合演示例子?sort_id=7452536


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK