知識點:
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>
資料實體類 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