项目

本文档有多个版本。请选择最适合您的选项。

UI
Database

Web 应用程序开发教程 - 第 2 部分:书籍列表页面

动态 JavaScript 代理

通常我们会从 JavaScript 端通过 AJAX 调用 HTTP API 端点。你可以使用 $.ajax 或其他工具调用端点。不过,ABP 提供了一种更好的方式。

ABP 会为所有 API 端点动态创建 JavaScript 代理。因此,你可以像调用 JavaScript 函数一样使用任何端点

在开发者控制台中测试

你可以使用浏览器开发者控制台轻松测试 JavaScript 代理。运行应用程序,打开浏览器的开发者工具快捷键通常是 F12),切换到 Console 标签页,输入以下代码并按回车:

acme.bookStore.books.book.getList({}).done(function (result) { console.log(result); });
  • acme.bookStore.booksBookAppService 的命名空间,已转换为驼峰命名法
  • bookBookAppService 的约定名称(移除 AppService 后缀并转换为驼峰命名法)。
  • getListCrudAppService 基类中定义的 GetListAsync 方法的约定名称(移除 Async 后缀并转换为驼峰命名法)。
  • {} 参数用于向 GetListAsync 方法发送空对象,该方法通常期望接收类型为 PagedAndSortedResultRequestDto 的对象,用于向服务器发送分页和排序选项(所有属性都是可选的且具有默认值,因此可以发送空对象)。
  • getList 函数返回一个 promise。你可以向 then(或 done)函数传递回调函数以获取从服务器返回的结果。

运行此代码会产生以下输出:

bookstore-javascript-proxy-console

你可以看到从服务器返回的书籍列表。你也可以查看开发者工具的 network 标签页来观察客户端与服务器之间的通信:

bookstore-getlist-result-network

现在,让我们使用 create 函数创建一本新书

acme.bookStore.books.book.create({
        name: 'Foundation',
        type: 7,
        publishDate: '1951-05-24',
        price: 21.5
    }).then(function (result) {
        console.log('successfully created the book with id: ' + result.id);
    });

如果你下载了本教程的源代码并按照示例步骤操作,则还需将 authorId 参数传递给 create 方法以创建新书

你应该会在控制台中看到类似以下的消息:

成功创建书籍,ID:439b0ea8-923e-8e1e-5d97-39f2c7ac4246

检查数据库中的 Books 表,查看新添加的书籍记录。你可以自行尝试 getupdatedelete 函数。

我们将在接下来的章节中使用这些动态代理函数与服务器通信。

本地化

在开始 UI 开发之前,我们首先需要准备本地化文本(通常是在开发应用程序时按需进行)。

本地化文本位于 Acme.BookStore.Domain.Shared 项目的 Localization/BookStore 文件夹下:

bookstore-localization-files

打开 en.json 文件(英文翻译),并按以下内容修改:

{
  "Culture": "en",
  "Texts": {
    "Menu:Home": "首页",
    "Welcome": "欢迎",
    "LongWelcomeMessage": "欢迎使用本应用程序。这是一个基于 ABP 的启动项目。更多信息请访问 abp.io。",
    "Menu:BookStore": "书店",
    "Menu:Books": "书籍",
    "Actions": "操作",
    "Close": "关闭",
    "Delete": "删除",
    "Edit": "编辑",
    "PublishDate": "出版日期",
    "NewBook": "新书",
    "Name": "名称",
    "Type": "类型",
    "Price": "价格",
    "CreationTime": "创建时间",
    "AreYouSure": "确定吗?",
    "AreYouSureToDelete": "确定要删除此项吗?",
    "Enum:BookType.0": "未定义",
    "Enum:BookType.1": "冒险",
    "Enum:BookType.2": "传记",
    "Enum:BookType.3": "反乌托邦",
    "Enum:BookType.4": "奇幻",
    "Enum:BookType.5": "恐怖",
    "Enum:BookType.6": "科学",
    "Enum:BookType.7": "科幻",
    "Enum:BookType.8": "诗歌"
  }
}
  • 本地化键名是任意的,你可以设置任何名称。我们针对特定文本类型推荐一些约定:
    • 为菜单项添加 Menu: 前缀。
    • 使用 Enum:<枚举类型>.<枚举值><枚举类型>.<枚举值> 命名约定来本地化枚举成员。这样处理后,ABP 可以在某些适当场景下自动对枚举进行本地化。

如果本地化文件中未定义某个文本,它将回退到本地化键(遵循 ASP.NET Core 的标准行为)。

ABP 的本地化系统基于 ASP.NET Core 的标准本地化系统构建,并在许多方面进行了扩展。详情请参阅本地化文档

创建书籍页面

是时候创建一些可见且可用的内容了!我们将采用 Razor Pages UI 方法,这是 Microsoft 推荐的方式,而非传统的 MVC。

Acme.BookStore.Web 项目的 Pages 文件夹下创建一个 Books 文件夹。右键单击 Books 文件夹,选择 添加 > Razor 页面 菜单项,添加一个新的 Razor 页面。将其命名为 Index

bookstore-add-index-page

打开 Index.cshtml 并将整个内容替换为以下内容:

@page
@using Acme.BookStore.Web.Pages.Books
@model IndexModel

<h2>书籍</h2>

Index.cshtml.cs 的内容应如下所示:

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Acme.BookStore.Web.Pages.Books;

public class IndexModel : PageModel
{
    public void OnGet()
    {

    }
}

将书籍页面添加到主菜单

打开 Menus 文件夹中的 BookStoreMenuContributor 类,并在 ConfigureMainMenuAsync 方法的末尾添加以下代码:

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* 登录应用程序,你将看到新的菜单项已添加到主菜单:

bookstore-menu-items

点击 书店 父菜单下的 Books 菜单项,你将重定向到新的空书籍页面。

书籍列表

我们将使用 Datatables.net jQuery 库来显示书籍列表。Datatables 库完全通过 AJAX 工作,速度快,流行度高,并提供良好的用户体验。

Datatables 库已在启动模板中配置,因此你可以直接在任意页面中使用它,无需为页面引入任何样式或脚本文件。

Index.cshtml

Pages/Books/Index.cshtml 修改为以下内容:

@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@model 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 代码量,并借助智能感知和编译时类型检查来避免错误。更多信息请查看 标签助手 文档。

Index.js

Pages/Books 文件夹下创建一个 Index.js 文件:

bookstore-index-js-file

文件内容如下:

$(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(acme.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 也是一个标准库,已在解决方案中预先配置,因此你可以方便地用它执行日期/时间操作。

所有配置选项请参阅 Datatables 文档

运行最终应用程序

现在你可以运行应用程序了!本部分的最终 UI 如下所示:

Book list

这是一个功能完整、服务器端分页、排序和本地化的书籍表格。

在本文档中