[ABP教程]第二章 圖書列表頁面


Web應用程序開發教程 - 第二章: 圖書列表頁面

關於本教程

在本系列教程中, 你將構建一個名為 Acme.BookStore 的用於管理書籍及其作者列表的基於ABP的應用程序. 它是使用以下技術開發的:

  • {{DB_Text}} 做為ORM提供程序.
  • {{UI_Value}} 做為UI框架.

本教程分為以下部分:

下載源碼

本教程根據你的UIDatabase偏好有多個版,我們准備了兩種可供下載的源碼組合:

{{if UI == "MVC"}}

動態JavaScript代理

通常在 JavaScript 端通過AJAX調用HTTP API端點. 你可以使用 $.ajax 或其他工具來調用端點. 但是ABP提供了更好的方法.

ABP動態為所有API端點創建 JavaScript代理. 所以你可以像調用Javascript本地方法一樣使用任何端點.

在開發者控制台中進行測試

你可以在自己喜歡的瀏覽器的開發者控制台輕松的測試JavaScript代理. 運行應用程序,打開瀏覽器的開發者人員工具(快捷鍵通常是F12),切換到控制台選項卡,輸入以下代碼然后按回車:

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

你可以看到服務端返回的 圖書列表. 你也可以在開發者人員工具的 網絡 選項卡查看客戶端到服務端的通信:

bookstore-getlist-result-network

Let's create a new book using the create function:

讓我們使用 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); 
    });

您應該在控制台中看到類似以下的消息:

successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246

檢查數據庫中的 Books 表你會看到新的一行. 你可以自己嘗試使用 get, updatedelete 函數.

我們將利用這些動態代理功能在接下來的章節來與服務器通信.

{{end}}

本地化

開始的UI開發之前,我們首先要准備本地化的文本(這是你通常在開發應用程序時需要做的).

本地化文本位於 Acme.BookStore.Domain.Shared 項目的 Localization/BookStore 文件夾下:

bookstore-localization-files

打開 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"
  }
}
  • 本地化關鍵字名稱是任意的. 你可以設置任何名稱. 對於特定的文本類型,我們更喜歡遵循一些約定:
    • 為按鈕項添加 Menu: 前綴.
    • 使用 Enum:<enum-type>:<enum-value> 命名約定來本地化枚舉成員. 當您這樣做時ABP可以在某些適當的情況下自動將枚舉本地化.

如果未在本地化文件中定義文本,則文本將回退到本地化鍵(作為ASP.NET Core的標准行為).

ABP本地化系統建立在ASP.NET Core標准本地化系統之上,並以多種方式進行了擴展. 有關詳細信息請參見本地化文檔.

{{if UI == "MVC"}}

創建圖書頁面

是時候創建可見的和可用的東西了! 代替經典的MVC,我們將使用微軟推薦的Razor Pages UI.

Acme.BookStore.Web 項目的 Pages 文件夾下創建一個名為新的 Books 的文件夾. 然后在文件夾右鍵選擇 添加 > Razor Page 菜單. 輸入名稱 Index:

bookstore-add-index-page

打開 Index.cshtml 並把內容修改成下面這樣:

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

<h2>Books</h2>

Index.cshtml.cs 內容應該是:

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Acme.BookStore.Web.Pages.Books
{
    public class IndexModel : PageModel
    {
        public void OnGet()
        {
  
        }
    }
}

將Book頁面添加到主菜單

打開 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

點擊BookStore下的Books子菜單項就會跳轉到空的圖書頁面.

圖書列表

We will use the Datatables.net jQuery library to show the book list. Datatables library completely work via AJAX, it is fast, popular and provides a good user experience.

我們將使用Datatables.netJQuery插件來顯示頁面上的表格列表. Datatables可以完全通過AJAX工作,速度快,並提供良好的用戶體驗.

Datatables插件在啟動模板中配置,因此你可以直接在任何頁面中使用它,無需在頁面中引用樣式和腳本文件.

Index.cshtml

Pages/Book/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 tag helper用於將外部的 腳本 添加到頁面中.它比標准的script標簽多了很多額外的功能.它可以處理 最小化版本.查看捆綁 & 壓縮文檔獲取更多信息.
  • abp-cardabp-table 是為Twitter Bootstrap的card component封裝的 tag helpers.ABP中有很多tag helpers,可以很方便的使用大多數bootstrap組件.你也可以使用原生的HTML標簽代替tag helpers.使用tag helper可以通過智能提示和編譯時類型檢查減少HTML代碼並防止錯誤.查看tag helpers 文檔.

Index.js

Pages/Books/ 文件夾中創建 index.js文件

bookstore-index-js-file

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.libs.datatables.createAjax是幫助ABP的動態JavaScript API代理跟Datatable的格式相適應的輔助方法.
  • acme.bookStore.books.book.getList 是動態JavaScript代理函數(上面已經介紹過了)
  • luxon 庫也是該解決方案中預先配置的標准庫,你可以輕松地執行日期/時間操作.

查看 Datatable文檔 了解更多配置項.

運行最終應用程序

你可以運行應用程序!該部分的最終用戶界面如下所示:

Book list

這是一個完全正常工作的服務端分頁,排序和本地化的圖書列表.

{{end}}

{{if UI == "NG"}}

安裝NPM包

注意: 本教程基於ABP Framework v3.0.3+. 如果你的項目版本較舊,請升級您的解決方案. 如果要升級現有的v2.x項目,請參閱遷移指南.

angular 目錄下打開命令行窗口,選擇 yarn 命令安裝NPM包:

yarn

創建圖書頁面

是時候創建可見和可用的東西了!開發ABP Angular前端應用程序時,需要使用一些工具:

BookModule

運行以下命令創建一個名為 BookModule 的新模塊:

yarn ng generate module book --module app --routing --route books

該命令應該產生以下的輸出:

> yarn ng generate module book --module app --routing --route books

yarn run v1.19.1
$ ng generate module book --module app --routing --route books
CREATE src/app/book/book-routing.module.ts (336 bytes)
CREATE src/app/book/book.module.ts (335 bytes)
CREATE src/app/book/book.component.html (19 bytes)
CREATE src/app/book/book.component.spec.ts (614 bytes)
CREATE src/app/book/book.component.ts (268 bytes)
CREATE src/app/book/book.component.scss (0 bytes)
UPDATE src/app/app-routing.module.ts (1289 bytes)
Done in 3.88s.

BookModule

打開 /src/app/book/book.module.ts 並使用以下內容替換:

import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { BookRoutingModule } from './book-routing.module';
import { BookComponent } from './book.component';

@NgModule({
  declarations: [BookComponent],
  imports: [
    BookRoutingModule,
    SharedModule
  ]
})
export class BookModule { }

  • 添加了 SharedModule. SharedModule 導出了一些創建用戶界面所需的通用模塊.
  • SharedModule 已經導出了 CommonModule,所以我們刪除了 CommonModule.

路由

生成的代碼將新的路由定義放在 src/app/app-routing.module.ts 文件中,如下所示:

const routes: Routes = [
  // other route definitions...
  { path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) },
];

現在打開 src/app/route.provider.ts 以下替換 configureRoutes 函數:

function configureRoutes(routes: RoutesService) {
  return () => {
    routes.add([
      {
        path: '/',
        name: '::Menu:Home',
        iconClass: 'fas fa-home',
        order: 1,
        layout: eLayoutType.application,
      },
      {
        path: '/book-store',
        name: '::Menu:BookStore',
        iconClass: 'fas fa-book',
        order: 2,
        layout: eLayoutType.application,
      },
      {
        path: '/books',
        name: '::Menu:Books',
        parentName: '::Menu:BookStore',
        layout: eLayoutType.application,
      },
    ]);
  };
}

RoutesService 是ABP框架提供的用於配置主菜單和路由的服務.

  • path 路由的URL.
  • name 菜單項的名稱(參閱本地化文檔了解更多).
  • iconClass 菜單項的圖標(你可以使用默認的Font Awesome圖標).
  • order 菜單項的排序.我們定義了101,它顯示在 "Administration" 項的后面.
  • layout BooksModule路由的布局. 可以定義 eLayoutType.application, eLayoutType.accounteLayoutType.empty.

更多信息請參閱RoutesService 文檔.

生成代理

ABP CLI提供了 generate-proxy 命令為你的服務HTTP API生成客戶端代理簡化客戶端使用服務的成本. 運行 generate-proxy 命令前你的host必須正在運行. 參閱 CLI 文檔.

angular 文件夾下運行以下命令:

abp generate-proxy

生成的文件如下:

Generated files

BookComponent

打開 /src/app/book/book.component.ts 用以下內容替換它:

import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto } from './models';
import { BookService } from './services';

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss'],
  providers: [ListService],
})
export class BookComponent implements OnInit {
  book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;

  constructor(public readonly list: ListService, private bookService: BookService) {}

  ngOnInit() {
    const bookStreamCreator = (query) => this.bookService.getListByInput(query);

    this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
      this.book = response;
    });
  }
}
  • 我們注入了生成的 BookService.
  • 我們實現了 ListService,它是一個公用服務,提供了簡單的分頁,排序和搜索.

打開 /src/app/book/book.component.html 用以下內容替換它:

<div class="card">
  <div class="card-header">
    <div class="row">
      <div class="col col-md-6">
        <h5 class="card-title">
          {%{{{ '::Menu:Books' | abpLocalization }}}%}
        </h5>
      </div>
      <div class="text-right col col-md-6"></div>
    </div>
  </div>
  <div class="card-body">
    <ngx-datatable [rows]="book.items" [count]="book.totalCount" [list]="list" default>
      <ngx-datatable-column [name]="'::Name' | abpLocalization" prop="name"></ngx-datatable-column>
      <ngx-datatable-column [name]="'::Type' | abpLocalization" prop="type">
        <ng-template let-row="row" ngx-datatable-cell-template>
          {%{{{ '::Enum:BookType:' + row.type | abpLocalization }}}%}
        </ng-template>
      </ngx-datatable-column>
      <ngx-datatable-column [name]="'::PublishDate' | abpLocalization" prop="publishDate">
        <ng-template let-row="row" ngx-datatable-cell-template>
          {%{{{ row.publishDate | date }}}%}
        </ng-template>
      </ngx-datatable-column>
      <ngx-datatable-column [name]="'::Price' | abpLocalization" prop="price">
        <ng-template let-row="row" ngx-datatable-cell-template>
          {%{{{ row.price | currency }}}%}
        </ng-template>
      </ngx-datatable-column>
    </ngx-datatable>
  </div>
</div>

現在你可以在瀏覽器看到最終結果:

Book list final result

{{end}}

下一章

查看本教程的下一章.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM