Web 应用开发教程 - 第 3 部分:创建、更新和删除书籍
创建新书
在本节中,你将学习如何创建一个新的模态对话框表单来创建新书。由于我们已继承自 AbpCrudPageBase,因此只需要开发视图部分。
添加“新建图书”按钮
打开 Books.razor 并将 <CardHeader> 部分替换为以下代码:
<CardHeader>
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Books"]</h2>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Column>
</Row>
</CardHeader>
这将通过向右侧添加一个“新建图书”按钮来更改卡片标题:
现在,我们可以添加一个模态框,当我们单击该按钮时它将打开。
图书创建模态框
打开 Books.razor 并将以下代码添加到页面末尾:
<Modal @ref="@CreateModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType.{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
此代码需要一个服务;在文件顶部,@inherits... 行之前注入 AbpBlazorMessageLocalizerHelper<T>:
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
- 该表单实现了验证,
AbpBlazorMessageLocalizerHelper用于简单地本地化验证消息。 CreateModal对象、CloseCreateModalAsync和CreateEntityAsync方法由基类定义。如果你想了解Modal和其他组件,请查看 Blazorise 文档。
到此为止。运行应用程序并尝试添加一本新书:
更新书籍
编辑书籍与创建新书类似。
操作下拉菜单
打开 Books.razor 并在 DataGridColumns 内添加以下 DataGridEntityActionsColumn 部分作为第一项:
<DataGridEntityActionsColumn TItem="BookDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
Clicked="() => OpenEditModalAsync(context)" />
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
OpenEditModalAsync在基类中定义,它接受要编辑的实体(书籍)。
DataGridEntityActionsColumn 组件用于在 DataGrid 中为每一行显示一个“操作”下拉菜单。如果其中只有一个可用操作,DataGridEntityActionsColumn 将显示一个单独的按钮而不是下拉菜单:
编辑模态框
我们现在可以定义一个模态框来编辑书籍。将以下代码添加到 Books.razor 页面的末尾:
<Modal @ref="@EditModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType.{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
Mapperly 配置
基类 AbpCrudPageBase 使用 对象到对象映射 系统将传入的 BookDto 对象转换为 CreateUpdateBookDto 对象。因此,我们需要定义映射。
打开 Acme.BookStore.MauiBlazor 项目中的 BookStoreBlazorMappers,并将内容更改为以下内容:
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly;
namespace Acme.BookStore.MauiBlazor;
[Mapper]
public partial class BookDtoToCreateUpdateBookDtoMapper : MapperBase<BookDto, CreateUpdateBookDto>
{
public override partial CreateUpdateBookDto Map(BookDto source);
public override partial void Map(BookDto source, CreateUpdateBookDto destination);
}
测试编辑模态框
你现在可以运行应用程序并尝试编辑一本书。
提示: 尝试将名称字段留空并提交表单以显示验证错误消息。
删除书籍
打开 Books.razor 页面,并在 EntityActions 内的“编辑”操作下添加以下 EntityAction 代码:
<EntityAction TItem="BookDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="() => GetDeleteConfirmationMessage(context)" />
DeleteEntityAsync在基类中定义,它通过向服务器发出调用来删除实体。ConfirmationMessage是一个回调函数,用于在执行操作前显示确认消息。GetDeleteConfirmationMessage在基类中定义。你可以重写此方法(或向ConfirmationMessage参数传递另一个值)来自定义本地化消息。
由于现在有两个操作,“操作”按钮变为一个下拉菜单:
运行应用程序并尝试删除一本书。
完整的 CRUD 用户界面代码
以下是创建书籍管理 CRUD 页面的完整代码,该页面是在最后两个部分中开发的:
@page "/books"
@using Volo.Abp.Application.Dtos
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Components.Web
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
@inherits AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card>
<CardHeader>
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Books"]</h2>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Column>
</Row>
</CardHeader>
<CardBody>
<DataGrid TItem="BookDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
CurrentPage="CurrentPage"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridEntityActionsColumn TItem="BookDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
Clicked="() => OpenEditModalAsync(context)" />
<EntityAction TItem="BookDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" />
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Name)"
Caption="@L["Name"]"></DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
<DisplayTemplate>
@L[$"Enum:BookType.{context.Type:D}"]
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.PublishDate)"
Caption="@L["PublishDate"]">
<DisplayTemplate>
@context.PublishDate.ToShortDateString()
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Price)"
Caption="@L["Price"]">
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.CreationTime)"
Caption="@L["CreationTime"]">
<DisplayTemplate>
@context.CreationTime.ToLongDateString()
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
<Modal @ref="@CreateModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType.{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
<Modal @ref="@EditModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType.{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
@code
{
public Books() // 构造函数
{
LocalizationResource = typeof(BookStoreResource);
}
}
抠丁客







