Chợ sánh: Cài đặt giao diện trang chủ
Back To BlogsTrong bài trước, chúng ta đã liên kết các mô đun cần thiết, trong bài này chúng ta sẽ cùng nhau tạo giao diện cho trang chủ.
Mục tiêu
- Giúp bạn lập trình được backend cho trang chủ.
- Giúp bạn lập trình được front-end cho trang chủ.
Lập trình trang chủ phía backend
Đầu tiên chúng ta sẽ cần tạo một lớp chứa thông tin về một cuốn sách có tên
WebBookResponse như sau:package org.youngmonkeys.bookstore.web.response; // các import @Getter @Builder public class WebBookResponse { private long id; private String code; private String name; private String authorName; private String authorUuid; private MediaNameModel bannerImage; private String shortedDescription; private String publisher; }
Ở đây chúng đang lấy một số thông tin cực kỳ cơ bản của một cuốn sách như id, mã, tên, tác giả, ảnh banner, ... Trong tương lai khi làm các tính năng phức tạp hơn, chúng ta sẽ bổ sung thêm các trường khác vào sau.
Tiếp theo chúng ta sẽ tạo một lớp để tổng hợp dữ liệu có tên
WebBookModelDecorator như sau:package org.youngmonkeys.bookstore.web.controller.decorator; // các import @EzySingleton @AllArgsConstructor public class WebBookModelDecorator { private final WebMediaService mediaService; private final WebPostService postService; private final WebProductBookService productBookService; private final WebProductDescriptionService productDescriptionService; private final WebUserService userService; private final WebBookStoreModelToResponseConverter modelToResponseConverter; public List<WebBookResponse> decorateToBookResponse( List<ProductModel> models ) { List<Long> productIds = newArrayList( models, ProductModel::getId ); Map<Long, ProductBookModel> bookById = productBookService .getProductBookMapByIds(productIds); Set<Long> userIds = bookById .values() .stream() .map(ProductBookModel::getAuthorUserId) .filter(it -> it > 0) .collect(Collectors.toSet()); Set<Long> mediaIds = models .stream() .map(ProductModel::getBannerImageId) .filter(it -> it > 0) .collect(Collectors.toSet()); Map<Long, Long> descriptionPostIdByProductId = productDescriptionService .getProductDescriptionPostIdMapByIds( productIds ); return Reactive.multiple() .register("userById", () -> userService.getUserMapByIds(userIds) ) .register("mediaById", () -> mediaService.getMediaNameMapByIds(mediaIds) ) .register("descriptionById", () -> postService.getPostMapByIds( descriptionPostIdByProductId.values() ) ) .blockingGet(map -> { Map<Long, UserModel> userById = map.get("userById"); Map<Long, MediaNameModel> mediaById = map.get("mediaById"); Map<Long, PostModel> descriptionById = map.get("descriptionById"); return newArrayList(models, it -> { ProductBookModel book = bookById.getOrDefault( it.getId(), ProductBookModel.builder().build() ); return modelToResponseConverter.toBookResponse( it, book, userById.getOrDefault( book.getAuthorUserId(), UserModel.builder().build() ), mediaById.get(it.getBannerImageId()), descriptionById.getOrDefault( descriptionPostIdByProductId.getOrDefault( it.getId(), ZERO_LONG ), PostModel.builder().build() ) ); }); }); } }
Lớp này sẽ giúp chúng ta tổng hợp các dữ liệu thành phần để tạo nên một danh sách thông tin các cuốn sách
WebBookResponse.Tiếp theo chúng ta sẽ cần tạo lớp
WebBookControllerService để lấy danh sách các cuốn sách nổi bật:package org.youngmonkeys.bookstore.web.controller.service; // các import @Service @AllArgsConstructor public class WebBookControllerService { private final WebProductService productService; private final WebBookModelDecorator bookModelDecorator; public List<WebBookResponse> getTopBooksByShopId( long shopId ) { List<ProductModel> models = productService .getProductsByShopIdAndProductTypeInAndStatusInSortByByDisplayOrderDescIdDesc( shopId, Collections.singletonList(BookStoreProductType.BOOK.toString()), Collections.singletonList(ProductStatus.PUBLISHED.toString()), 0, 1 ); return bookModelDecorator.decorateToBookResponse(models); } }
Ở đây chúng ta đang lấy ra các sản phẩm có kiểu là sách
BOOK, có trạng thái đã được xuất bản PUBLISHED, với thứ hiển thị từ cao xuống thấp và tạm thời chúng ta chỉ lấy một cuốn sách duy nhất, trong tương lai chúng ta sẽ lấy ra nhiều cuốn sách hơn.Tiếp theo chúng ta sẽ tạo ra một lớp có tên
ViewFactory:package org.youngmonkeys.bookstore.web.view; // các import @EzySingleton @AllArgsConstructor public class ViewFactory { private final WebPageFragmentManager pageFragmentManager; private final WebShopService shopService; private final WebBookControllerService bookControllerService; public View.Builder newHomeViewBuilder(String language) { long shopId = shopService.getDefaultShopId(); return View.builder() .template("home") .addVariable("pageTitle", "home") .addVariable( "topBooks", bookControllerService.getTopBooksByShopId(shopId) ) .addVariable( "fragments", pageFragmentManager.getPageFragmentMap( "home", language ) ); } }
Lớp này đóng gói việc khởi tạo ra trang chủ, bởi vì ở trang chủ còn có các trạng thái như mở modal đăng nhập, đăng ký nên nó sẽ dùng ở nhiều chỗ nên chúng ta cần đóng gói thông qua lớp factory này.
Tiếp theo, ở
book-store-web-plugin chúng ta sẽ tạo ra lớp HomeController:package org.youngmonkeys.bookstore.web.controller.view; // các import @Setter public class HomeController { @EzyAutoBind private ViewFactory viewFactory; @EzyAutoBind private WebLanguageControllerService languageControllerService; @DoGet("/") public View homeGet( HttpServletRequest request ) { String language = languageControllerService .getLanguageCodeOrDefault(request); return viewFactory .newHomeViewBuilder(language) .build(); } }
Ở đây chúng ta đang khai báo controller cho uri
/ chính là trang chủ, lớp này sẽ chỉ đơn giản là gọi đến ViewFactory để tạo view mà thôi.Tiếp theo ở
book-store-theme chúng ta sẽ tạo ra lớp WebBookStoreHomeController:package org.youngmonkeys.bookstore.web.controller.view; // các import @Controller public class WebBookStoreHomeController extends HomeController {}
Lớp này chỉ đơn giản là thừa kế lại
HomeController.Tại sao lại phải phức tạp như vậy? Như chúng ta đã biết thì EzyPlatform sẽ chỉ cho một theme được cài tại một thời điểm nhưng plugin thì lại có thể nhiều. Khi một ai đó cài theme
book-store họ có thể tự code ra một giao diện mới thay vì dùng giao diện mặc định do chúng ta tạo ra, lúc này họ có có thể thừa kế HomeController hoặc tự cài đặt một cái mới.Bây giờ, bạn hãy mở admin bằng cách chạy
BookStoreAdminPluginStartupTest và truy cập vào các sản phẩm. Sau đó tạo một sản phẩm với thông tin kiểu thế này:

Như vậy là xong, bây giờ chúng ta có thể chạy lại
BookStoreThemeStartupTest và kết quả chúng ta nhận được sẽ là:
Lập trình trang chủ phía frontend
Như chúng ta thấy, giao diện trang chủ hiện tại hơi thô kệch, chúng ta hãy thay đổi html của
home.html một chút như sau:<div layout:fragment="content"> <div class="common-spacing-1"></div> <div class="container"> <div th:each="topBook : " class="main-book"> <div class="description-item"> <h1 class="display-2 text-dark"> [[]] </h1> <div class="lead" th:utext=""></div> <p class="author text-muted mb-0" th:text=""></p> </div> <div class="image-item"> <img th:if="" th:src=""> </div> </div> </div> </div>
Ở đây chúng ta thêm một khoảng trắng ở trên đầu, bổ sung thêm tiên tác giả.
Chúng ta cũng bổ sung thêm vào
home.css:
.main-book .description-item .lead {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
overflow: hidden;
}
.main-book .description-item .author {
margin-top: 12px;
font-size: 2rem;
}
.main-book .image-item {
width: 50%;
display: flex;
justify-content: end;
}
Để giới hạn số dòng mô tả sách và cho tên tác giả nổi bật hơn. Bây giờ refresh lại trang và kết quả chúng ta nhận được là:

Giao diện đã trông đẹp và rõ ràng hơn rất nhiều.