ABP框架入门学习(三) ——UI展现层增删改查实现

上一篇文章咋们说道新增的BOOK模块,从实体到领域层再到应用层,自动生成出来的swagger也完成,接下来咋们直接使用上面所封装的给展现层的函数

前言准备工作:

1、首先运行项目,在开发者模式(浏览器F12)测试getList和Create功能

 

 

 

  • testApp.bookStore.booksBookAppService转换为camelCase的命名空间。
  • bookBookAppService(删除AppService后缀并转换为驼峰式)的常规名称。
  • getListGetListAsync是在基类中定义的方法的常规名称CrudAppService(删除了Async后缀并转换为 camelCase)。
  • {}参数用于向方法发送一个空对象,该GetListAsync方法通常需要一个类型的对象,该对象PagedAndSortedResultRequestDto用于向服务器发送分页和排序选项(所有属性都是可选的,具有默认值,因此您可以发送一个空对象)。
  • getList函数返回一个promise您可以将回调传递给then(or done) 函数以获取从服务器返回的结果。

 

 

 

检查Books数据库中的表以查看新书行。您可以尝试getupdatedelete自己运行。

 

2、文本本地化

由于我们后面会使用了很多本地化文本,所以需要将它们添加到本地化文件(en.json在项目TestApp.BookStore.Domain.Shared下Localization/BookStore文件夹下),如:@L["Books"]、@L["NewBook"]等

 脚本如下:

{
  "culture": "en",
  "texts": {
    "Menu:Home": "Home",
    "Welcome": "Welcome",
    "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.",
    "Menu:BookStore": "Book Store",
    "Menu:Books": "Books",
    "Actions": "Actions",
    "Close": "Close",
    "Delete": "Delete",
    "Edit": "Edit",
    "PublishDate": "Publish date",
    "NewBook": "New book",
    "Name": "Name",
    "Type": "Type",
    "Price": "Price",
    "CreationTime": "Creation time",
    "AreYouSure": "Are you sure?",
    "AreYouSureToDelete": "Are you sure you want to delete this item?",
    "Enum:BookType:0": "Undefined",
    "Enum:BookType:1": "Adventure",
    "Enum:BookType:2": "Biography",
    "Enum:BookType:3": "Dystopia",
    "Enum:BookType:4": "Fantastic",
    "Enum:BookType:5": "Horror",
    "Enum:BookType:6": "Science",
    "Enum:BookType:7": "Science fiction",
    "Enum:BookType:8": "Poetry"
  }
}
  • 本地化键名是任意的。您可以设置任何名称。我们更喜欢针对特定文本类型的一些约定;
    • Menu:为菜单项添加前缀。
    • 使用Enum:<enum-type>:<enum-value>命名约定来本地化枚举成员。当你这样做时,ABP 可以在某些适当的情况下自动本地化枚举。

 

——————————UI项目增删改查功能开始——————————

 

一、创建图书页面(查)

 在项目TestApp.BookStore.Web创建一个文件夹Books通过右键单击 Books 文件夹然后选择Add > Razor Page菜单项来添加新的 Razor Page。将其命名为Index

 

 

 

添加书籍主菜单

打开项目TestApp.BookStore.Web项目下Menus文件夹中的BookStoreMenuContributor类,在方法末尾添加如下代码:

 

context.Menu.AddItem(
            new ApplicationMenuItem(
                    "BooksStore",
                    l["Menu:BookStore"],
                    icon: "fa fa-book"
                ).AddItem(
                    new ApplicationMenuItem(
                        "BooksStore.Books",
                        l["Menu:Books"],
                        url: "/Books"
                    )
                )
            );

运行项目,使用用户名admin和密码登录应用程序1q2w3E*,您可以看到新的菜单项已添加到主菜单中:

 

 

 实现书籍列表功能

修改Index.cshtml页面

@page
@using TestApp.BookStore.Localization
@using TestApp.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@model TestApp.BookStore.Web.Pages.Books.IndexModel
@inject IStringLocalizer<BookStoreResource> L
@section scripts
{
    <abp-script src="/Pages/Books/Index.js"/>
}

<abp-card>
    <abp-card-header>
        <h2>@L["Books"]</h2>
    </abp-card-header>
    <abp-card-body>
        <abp-table striped-rows="true" id="BooksTable"></abp-table>
    </abp-card-body>
</abp-card>
  • abp-script 标签助手用于向页面添加外部脚本script与标准标签相比,它具有许多附加功能。它处理缩小版本控制查看捆绑和缩小文档以获取详细信息。
  • abp-card是 Twitter Bootstrap 的卡片组件的标签助手。ABP 框架提供了其他有用的标签助手,可以轻松使用大多数 bootstrap的组件。您可以使用常规 HTML 标签代替这些标签助手,但使用标签助手可减少 HTML 代码并通过 IntelliSense 的帮助防止错误并编译时间类型检查。有关更多信息,请查看标签助手文档。

Pages/Books下新增Index.js文件,脚本如下:

$(function () {
    var l = abp.localization.getResource('BookStore');

    var dataTable = $('#BooksTable').DataTable(
        abp.libs.datatables.normalizeConfiguration({
            serverSide: true,
            paging: true,
            order: [[1, "asc"]],
            searching: false,
            scrollX: true,
            ajax: abp.libs.datatables.createAjax(testapp.bookStore.books.book.getList),
            columnDefs: [
                {
                    title: l('Name'),
                    data: "name"
                },
                {
                    title: l('Type'),
                    data: "type",
                    render: function (data) {
                        return l('Enum:BookType:' + data);
                    }
                },
                {
                    title: l('PublishDate'),
                    data: "publishDate",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString();
                    }
                },
                {
                    title: l('Price'),
                    data: "price"
                },
                {
                    title: l('CreationTime'), data: "creationTime",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString(luxon.DateTime.DATETIME_SHORT);
                    }
                }
            ]
        })
    );
})
  • abp.localization.getResource获取一个函数,该函数用于使用在服务器端定义的相同 JSON 文件来本地化文本。通过这种方式,您可以与客户端共享本地化值。
  • abp.libs.datatables.normalizeConfiguration是 ABP 框架定义的辅助函数。不需要使用它,但它通过为缺少的选项提供常规默认值来简化Datatables配置。
  • abp.libs.datatables.createAjax是另一个帮助函数,用于使 ABP 的动态 JavaScript API 代理适应Datatable的预期参数格式
  • acme.bookStore.books.book.getList就是之前介绍的动态JavaScript代理功能。
  • luxon库也是解决方案中预配置的标准库,因此您可以使用它轻松执行日期/时间操作。

运行项目,可以查看书籍列表,如下图:

 

 

 

 

二、创建新书(增)

书店创建对话框

创建模态表单

在项目TestApp.BookStore.Web下Pages/Books的文件夹下创建一个名CreateModal.cshtml的Razor页面

 

 

 

CreateModal.cshtml

打开CreateModal.cshtml文件并粘贴以下代码:

@page
@using TestApp.BookStore.Localization
@using TestApp.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model TestApp.BookStore.Web.Pages.Books.CreateModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
    Layout = null;
}

<abp-dynamic-form abp-model="Book" asp-page="/Books/CreateModal">
    <abp-modal>
        <abp-modal-header title="@L["NewBook"].Value"></abp-modal-header>
        <abp-modal-body>
            <abp-form-content />
        </abp-modal-body>
        <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
    </abp-modal>
</abp-dynamic-form>

 

  • 此模式使用abp-dynamic-form 标签助手自动从CreateUpdateBookDto模型类创建表单。
  • abp-model属性指示模型对象,Book在这种情况下它是属性。
  • abp-form-content标签助手是呈现表单控件的占位符(它是可选的,仅当您在abp-dynamic-form标签中添加了一些其他内容时才需要,就像在这个页面中一样)。

提示:Layout应该null和本例中一样,因为我们不想在通过 AJAX 加载模态框时包含所有布局。

CreateModal.cshtml.cs

打开CreateModal.cshtml.cs文件(CreateModalModel类)并将其替换为以下代码:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Threading.Tasks;
using TestApp.BookStore.Books;
using Microsoft.AspNetCore.Mvc;

namespace TestApp.BookStore.Web.Pages.Books
{
    public class CreateModalModel : BookStorePageModel
    {
        [BindProperty]
        public CreateUpdateBookDto Book { get; set; }

        private readonly IBookAppService _bookAppService;

        public CreateModalModel(IBookAppService bookAppService)
        {
            _bookAppService = bookAppService;
        }

        public void OnGet()
        {
            Book = new CreateUpdateBookDto();
        }

        public async Task<IActionResult> OnPostAsync()
        {
            await _bookAppService.CreateAsync(Book);
            return NoContent();
        }
    }
}

 

  • 此类派生自BookStorePageModel而不是标准PageModelBookStorePageModel间接继承PageModel并添加了一些可以在页面模型类中共享的通用属性和方法。
  • [BindProperty]属性上的Book属性将发布请求数据绑定到此属性。
  • 此类只是IBookAppService在构造函数中注入 并调用处理程序CreateAsync中的方法OnPostAsync
  • 它在方法中创建一个新CreateUpdateBookDto对象OnGetASP.NET Core 可以在不创建这样的新实例的情况下工作。但是,它不会为您创建实例,并且如果您的类在类构造函数中有一些默认值分配或代码执行,它们将不起作用。CreateUpdateBookDto对于这种情况,我们为某些属性设置了默认值。

 

调整Index页面,添加“新书”按钮

脚本如下:

@page
@using TestApp.BookStore.Localization
@using TestApp.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@model TestApp.BookStore.Web.Pages.Books.IndexModel
@inject IStringLocalizer<BookStoreResource> L
@section scripts
{
    <abp-script src="/Pages/Books/Index.js"/>
}

<abp-card>
    <abp-card-header>
        <abp-row>
            <abp-column size-md="_6">
                <abp-card-title>@L["Books"]</abp-card-title>
            </abp-column>
            <abp-column size-md="_6" class="text-right">
                <abp-button id="NewBookButton"
                            text="@L["NewBook"].Value"
                            icon="plus"
                            button-type="Primary"/>
            </abp-column>
        </abp-row>
    </abp-card-header>
    <abp-card-body>
        <abp-table striped-rows="true" id="BooksTable"></abp-table>
    </abp-card-body>
</abp-card>

 

这会在表格右上角添加一个名为New book的新按钮:

 

 

 

实现新增按钮事件,修改Pages/Books/Index.js文件

var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');

    createModal.onResult(function () {
        dataTable.ajax.reload();
    });

    $('#NewBookButton').click(function (e) {
        e.preventDefault();
        createModal.open();
    });

 

  • abp.ModalManager是一个帮助类来管理客户端的模态。它在内部使用 Twitter Bootstrap 的标准模式,但通过提供简单的 API 抽象了许多细节。
  • createModal.onResult(...)用于创建新书后刷新数据表。
  • createModal.open();用于打开模型以创建新书。

文件的最终内容Index.js应该是这样的:

$(function () {
    var l = abp.localization.getResource('BookStore');
    //获取列表
    var dataTable = $('#BooksTable').DataTable(
        abp.libs.datatables.normalizeConfiguration({
            serverSide: true,
            paging: true,
            order: [[1, "asc"]],
            searching: false,
            scrollX: true,
            ajax: abp.libs.datatables.createAjax(testApp.bookStore.books.book.getList),
            columnDefs: [
                {
                    title: l('Name'),
                    data: "name"
                },
                {
                    title: l('Type'),
                    data: "type",
                    render: function (data) {
                        return l('Enum:BookType:' + data);
                    }
                },
                {
                    title: l('PublishDate'),
                    data: "publishDate",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString();
                    }
                },
                {
                    title: l('Price'),
                    data: "price"
                },
                {
                    title: l('CreationTime'), data: "creationTime",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString(luxon.DateTime.DATETIME_SHORT);
                    }
                }
            ]
        })
    );

    //新增操作
    var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');

    createModal.onResult(function () {
        dataTable.ajax.reload();
    });

    $('#NewBookButton').click(function (e) {
        e.preventDefault();
        createModal.open();
    });
})

 

运行应用程序并使用新的模态表单添加一些新书。

三、更新书籍信息(改)

在项目TestApp.BookStore.Web下Pages/Books的文件夹下创建一个名EditModal.cshtml的Razor页面

 

 

 

EditModal.cshtml

EditModal.cshtml内容替换为以下内容:

 

此页面与 非常相似CreateModal.cshtml,除了:

  • 它包括一个abp-input用于Id存储Id编辑书的属性(这是一个隐藏的输入)。
  • Books/EditModal用作发布 URL。

EditModal.cshtml.cs

打开EditModal.cshtml.cs文件(EditModalModel类)并将其替换为以下代码:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.Threading.Tasks;
using TestApp.BookStore.Books;

namespace TestApp.BookStore.Web.Pages.Books
{
    public class EditModalModel : BookStorePageModel
    {
        [HiddenInput]
        [BindProperty(SupportsGet = true)]
        public Guid Id { get; set; }

        [BindProperty]
        public CreateUpdateBookDto Book { get; set; }

        private readonly IBookAppService _bookAppService;

        public EditModalModel(IBookAppService bookAppService)
        {
            _bookAppService = bookAppService;
        }

        public async Task OnGetAsync()
        {
            var bookDto = await _bookAppService.GetAsync(Id);
            Book = ObjectMapper.Map<BookDto, CreateUpdateBookDto>(bookDto);
        }

        public async Task<IActionResult> OnPostAsync()
        {
            await _bookAppService.UpdateAsync(Id, Book);
            return NoContent();
        }
    }
}

 

  • [HiddenInput]并且[BindProperty]是标准的 ASP.NET Core MVC 属性。SupportsGet用于能够Id从请求的查询字符串参数中获取值。
  • 在该OnGetAsync方法中,我们BookDto从 中获取 ,BookAppService并将其映射到 DTO 对象CreateUpdateBookDto
  • 用于更新实体OnPostAsync用途。BookAppService.UpdateAsync(...)

从 BookDto 映射到 CreateUpdateBookDto

为了能够映射BookDtoCreateUpdateBookDto,配置一个新的映射。为此,请TestApp.BookStore.Web项目中打开文件BookStoreWebAutoMapperProfile.cs并进行更改,如下所示:

using AutoMapper;
using TestApp.BookStore.Books;

namespace TestApp.BookStore.Web;

public class BookStoreWebAutoMapperProfile : Profile
{
    public BookStoreWebAutoMapperProfile()
    {
        //Define your AutoMapper configuration here for the Web project.
        CreateMap<BookDto, CreateUpdateBookDto>();
    }
}

请注意,我们将在 web 层中进行映射定义作为最佳实践,因为它仅在该层中需要。

 

将“操作”下拉列表添加到表中

打开Pages/Books/Index.js文件并替换如下内容:

$(function () {
    var l = abp.localization.getResource('BookStore');

    var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
    var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');
    //获取列表
    var dataTable = $('#BooksTable').DataTable(
        abp.libs.datatables.normalizeConfiguration({
            serverSide: true,
            paging: true,
            order: [[1, "asc"]],
            searching: false,
            scrollX: true,
            ajax: abp.libs.datatables.createAjax(testApp.bookStore.books.book.getList),
            columnDefs: [
                {
                    title: l('Actions'),
                    rowAction: {
                        items:
                            [
                                {
                                    text: l('Edit'),
                                    action: function (data) {
                                        editModal.open({ id: data.record.id });
                                    }
                                }
                            ]
                    }
                },
                {
                    title: l('Name'),
                    data: "name"
                },
                {
                    title: l('Type'),
                    data: "type",
                    render: function (data) {
                        return l('Enum:BookType:' + data);
                    }
                },
                {
                    title: l('PublishDate'),
                    data: "publishDate",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString();
                    }
                },
                {
                    title: l('Price'),
                    data: "price"
                },
                {
                    title: l('CreationTime'), data: "creationTime",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString(luxon.DateTime.DATETIME_SHORT);
                    }
                }
            ]
        })
    );

    //新增操作
    var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');

    createModal.onResult(function () {
        dataTable.ajax.reload();
    });

    $('#NewBookButton').click(function (e) {
        e.preventDefault();
        createModal.open();
    });
})
  • 添加了一个新ModalManager名称editModal以打开编辑模式对话框。
  • 在该部分的开头添加了一个新列columnDefs此列用于“操作”下拉按钮。
  • 编辑”操作只是调用editModal.open()打开编辑对话框。
  • editModal.onResult(...)您关闭编辑模式时,回调会刷新数据表。

 

运行应用程序并编辑修改书籍信息

最终的 UI 如下所示:

 

 

 

四、删除书籍(删)

打开Pages/Books/Index.js文件并将新项目添加到rowAction> items

{
                                    text: l('Delete'),
                                    confirmMessage: function (data) {
                                        return l(
                                            'BookDeletionConfirmationMessage',
                                            data.record.name
                                        );
                                    },
                                    action: function (data) {
                                        testApp.bookStore.books.book
                                            .delete(data.record.id)
                                            .then(function () {
                                                abp.notify.info(
                                                    l('SuccessfullyDeleted')
                                                );
                                                dataTable.ajax.reload();
                                            });
                                    }
                                }
  • confirmMessage选项用于在执行之前询问确认问题action
  • acme.bookStore.books.book.delete(...)方法向服务器发出 AJAX 请求以删除一本书。
  • abp.notify.info()删除操作后显示通知。

由于我们使用了两个新的本地化文本(BookDeletionConfirmationMessageSuccessfullyDeleted),您需要将它们添加到本地化文件(en.json在项目TestApp.BookStore.Domain.Shared下Localization/BookStore文件夹下):

    "BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?",
    "SuccessfullyDeleted": "Successfully deleted!"

en.json最终脚本如下:

{
  "culture": "en",
  "texts": {
    "Menu:Home": "Home",
    "Welcome": "Welcome",
    "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.",
    "Menu:BookStore": "Book Store",
    "Menu:Books": "Books",
    "Actions": "Actions",
    "Close": "Close",
    "Delete": "Delete",
    "Edit": "Edit",
    "PublishDate": "Publish date",
    "NewBook": "New book",
    "Name": "Name",
    "Type": "Type",
    "Price": "Price",
    "CreationTime": "Creation time",
    "AreYouSure": "Are you sure?",
    "AreYouSureToDelete": "Are you sure you want to delete this item?",
    "Enum:BookType:0": "Undefined",
    "Enum:BookType:1": "Adventure",
    "Enum:BookType:2": "Biography",
    "Enum:BookType:3": "Dystopia",
    "Enum:BookType:4": "Fantastic",
    "Enum:BookType:5": "Horror",
    "Enum:BookType:6": "Science",
    "Enum:BookType:7": "Science fiction",
    "Enum:BookType:8": "Poetry",
    "BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?",
    "SuccessfullyDeleted": "Successfully deleted!"
  }
}

 

最终Page/Books/Index.js内容如下图:

$(function () {
    var l = abp.localization.getResource('BookStore');

    //获取列表
    var dataTable = $('#BooksTable').DataTable(
        abp.libs.datatables.normalizeConfiguration({
            serverSide: true,
            paging: true,
            order: [[1, "asc"]],
            searching: false,
            scrollX: true,
            ajax: abp.libs.datatables.createAjax(testApp.bookStore.books.book.getList),
            columnDefs: [
                {
                    title: l('Actions'),
                    rowAction: {
                        items:
                            [
                                {
                                    text: l('Edit'),
                                    action: function (data) {
                                        editModal.open({ id: data.record.id });
                                    }
                                },
                                {
                                    text: l('Delete'),
                                    confirmMessage: function (data) {
                                        return l(
                                            'BookDeletionConfirmationMessage',
                                            data.record.name
                                        );
                                    },
                                    action: function (data) {
                                        testApp.bookStore.books.book
                                            .delete(data.record.id)
                                            .then(function () {
                                                abp.notify.info(
                                                    l('SuccessfullyDeleted')
                                                );
                                                dataTable.ajax.reload();
                                            });
                                    }
                                }
                            ]
                    }
                },
                {
                    title: l('Name'),
                    data: "name"
                },
                {
                    title: l('Type'),
                    data: "type",
                    render: function (data) {
                        return l('Enum:BookType:' + data);
                    }
                },
                {
                    title: l('PublishDate'),
                    data: "publishDate",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString();
                    }
                },
                {
                    title: l('Price'),
                    data: "price"
                },
                {
                    title: l('CreationTime'), data: "creationTime",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString(luxon.DateTime.DATETIME_SHORT);
                    }
                }
            ]
        })
    );

    //新增操作
    var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');

    createModal.onResult(function () {
        dataTable.ajax.reload();
    });

    $('#NewBookButton').click(function (e) {
        e.preventDefault();
        createModal.open();
    });

    //修改操作(columnDefs新增Actions操作列)
    var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');
    editModal.onResult(function () {
        dataTable.ajax.reload();
    });

})

 

运行应用程序并尝试删除书籍信息。

 

 

 

 至此,Book板块的增删改查展现层完成,在此记录,方便读者参考学习!

 

posted @ 2022-02-25 15:49  HI_Hub_MI  阅读(1115)  评论(2编辑  收藏  举报