上一篇文章咋們說道新增的BOOK模塊,從實體到領域層再到應用層,自動生成出來的swagger也完成,接下來咋們直接使用上面所封裝的給展現層的函數
前言准備工作:
1、首先運行項目,在開發者模式(瀏覽器F12)測試getList和Create功能
testApp.bookStore.books
是BookAppService
轉換為camelCase的命名空間。book
是BookAppService
(刪除AppService
后綴並轉換為駝峰式)的常規名稱。getList
GetListAsync
是在基類中定義的方法的常規名稱CrudAppService
(刪除了Async
后綴並轉換為 camelCase)。- 該
{}
參數用於向方法發送一個空對象,該GetListAsync
方法通常需要一個類型的對象,該對象PagedAndSortedResultRequestDto
用於向服務器發送分頁和排序選項(所有屬性都是可選的,具有默認值,因此您可以發送一個空對象)。 - 該
getList
函數返回一個promise
. 您可以將回調傳遞給then
(ordone
) 函數以獲取從服務器返回的結果。
檢查Books
數據庫中的表以查看新書行。您可以嘗試get
,update
並delete
自己運行。
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*
,您可以看到新的菜單項已添加到主菜單中:
實現書籍列表功能
@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
而不是標准PageModel
。BookStorePageModel
間接繼承PageModel
並添加了一些可以在頁面模型類中共享的通用屬性和方法。 [BindProperty]
屬性上的Book
屬性將發布請求數據綁定到此屬性。- 此類只是
IBookAppService
在構造函數中注入 並調用處理程序CreateAsync
中的方法OnPostAsync
。 - 它在方法中創建一個新
CreateUpdateBookDto
對象OnGet
。ASP.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
為了能夠映射BookDto
到CreateUpdateBookDto
,配置一個新的映射。為此,請在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()
刪除操作后顯示通知。
由於我們使用了兩個新的本地化文本(BookDeletionConfirmationMessage
和SuccessfullyDeleted
),您需要將它們添加到本地化文件(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板塊的增刪改查展現層完成,在此記錄,方便讀者參考學習!