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.books是BookAppService的命名空间,已转换为驼峰命名法。book是BookAppService的约定名称(移除AppService后缀并转换为驼峰命名法)。getList是CrudAppService基类中定义的GetListAsync方法的约定名称(移除Async后缀并转换为驼峰命名法)。{}参数用于向GetListAsync方法发送空对象,该方法通常期望接收类型为PagedAndSortedResultRequestDto的对象,用于向服务器发送分页和排序选项(所有属性都是可选的且具有默认值,因此可以发送空对象)。getList函数返回一个promise。你可以向then(或done)函数传递回调函数以获取从服务器返回的结果。
运行此代码会产生以下输出:
你可以看到从服务器返回的书籍列表。你也可以查看开发者工具的 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 表,查看新添加的书籍记录。你可以自行尝试 get、update 和 delete 函数。
我们将在接下来的章节中使用这些动态代理函数与服务器通信。
本地化
在开始 UI 开发之前,我们首先需要准备本地化文本(通常是在开发应用程序时按需进行)。
本地化文本位于 Acme.BookStore.Domain.Shared 项目的 Localization/BookStore 文件夹下:
打开 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:
打开 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* 登录应用程序,你将看到新的菜单项已添加到主菜单:
点击 书店 父菜单下的 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 文件:
文件内容如下:
$(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 如下所示:
这是一个功能完整、服务器端分页、排序和本地化的书籍表格。
抠丁客









