Giới thiệu Đây là bài viết hợp tác được tài trợ bởi . OneEntry CMS Xây dựng một ứng dụng thương mại điện tử thường là một nhiệm vụ đầy thách thức. Với rất nhiều lựa chọn thay thế có sẵn, không dễ để chọn được nhóm công nghệ phù hợp với yêu cầu của dự án, nhu cầu về khả năng mở rộng và tính bền vững lâu dài. Một điểm quan trọng khác là các dự án Thương mại điện tử xử lý nhiều dữ liệu và hoạt động CRUD. Việc tạo ra một hệ thống phụ trợ vững chắc, có thể mở rộng và an toàn có thể mất rất nhiều thời gian ngay cả đối với hầu hết các nhà phát triển có kinh nghiệm. Tôi đã chọn một nhóm công nghệ dựa trên NextJS, TypeScript, Tailwind CSS và OneEntry CMS. Chúng tôi sẽ tự mình xây dựng một dự án Thương mại điện tử thực tế để xem nó hoạt động cùng nhau như thế nào và có thể sử dụng nó như thế nào để đơn giản hóa việc quản lý nội dung. Mã cho dự án này sẽ có sẵn trong . kho GitHub Sự lựa chọn của ngăn xếp công nghệ một khung React để xây dựng các ứng dụng web nhanh và hiệu quả, đi kèm với các tính năng như kết xuất máy khách và máy chủ, tìm nạp dữ liệu, xử lý tuyến đường, phần mềm trung gian, tối ưu hóa tích hợp, v.v. NextJS là thêm tính năng nhập tĩnh vào JavaScript giúp dễ dàng phát hiện và sửa lỗi cho các dự án có thể mở rộng như Thương mại điện tử. Nó cũng tăng năng suất thông qua các tính năng như tự động hoàn thành và hỗ trợ tái cấu trúc. TypeScript tăng tốc phần tạo kiểu của ứng dụng web, cho phép nhà phát triển tạo kiểu cho các thành phần trong phần đánh dấu mà không cần phải chuyển đổi giữa các tệp CSS bên ngoài và đặt tên lớp cho từng tệp. Tailwind CSS là một hệ thống quản lý nội dung không cần đầu với giao diện dễ sử dụng, phần phụ trợ dễ dàng mở rộng, API nhanh và tài liệu rõ ràng để tăng năng suất của bạn cho trải nghiệm quản lý và tạo nội dung trang web. OneEntry CMS Nội dung và Thiết kế Trang đích sẽ hiển thị tiêu đề tiêu đề, liệt kê các tính năng của cửa hàng và bao gồm hình ảnh anh hùng. Phần cửa hàng đầu tiên sẽ dành riêng cho Quần áo. Phần cửa hàng thứ hai sẽ bao gồm Gear. Mỗi mục sẽ có một trang Xem trước riêng với thông tin chi tiết. Các mặt hàng đã có trong giỏ hàng sẽ có tùy chọn để loại bỏ chúng. Giỏ hàng sẽ liệt kê tất cả các mặt hàng đã chọn và tính tổng. Tạo dự án OneEntry Đầu tiên, người dùng sẽ cần phải đăng ký một tài khoản mới. Để làm điều đó, hãy điều hướng đến trang chủ OneEntry và qua tài khoản email của bạn. đăng ký Sau đó, và bạn sẽ được chuyển đến bảng điều khiển OneEntry. đăng nhập Bắt đầu bằng cách tạo một dự án mới. Bạn sẽ nhận được mã miễn phí để sử dụng gói học trong một tháng. Bạn sẽ có cơ hội kích hoạt nó trong quá trình tạo dự án. Việc tạo dự án sẽ mất vài phút. Khi đã sẵn sàng, trạng thái dự án sẽ thay đổi thành "Đang làm việc" và chỉ báo trạng thái sẽ có màu xanh lục. Tạo các trang Sau khi dự án được tạo, bạn sẽ nhận được email có thông tin đăng nhập chi tiết để truy cập vào cổng CMS của mình nhằm tạo và lưu trữ dữ liệu cho ứng dụng. Sau khi đăng nhập, bạn sẽ có thể tạo trang đầu tiên của mình. Điều hướng đến Quản lý nội dung, nhấp vào Tạo trang mới và điền tất cả dữ liệu được yêu cầu - loại trang, tiêu đề trang, ULR trang và tên của mục menu. Tất cả dữ liệu sẽ được lưu tự động khi nhập. Tạo 4 trang khác nhau cho Trang chủ, Quần áo, Thiết bị và Xe đẩy. Sau khi tạo, các trang sẽ trông giống như trong ảnh chụp màn hình bên dưới. Tạo thuộc tính Tiếp theo, chúng ta cần tạo cấu trúc dữ liệu mà chúng ta sẽ lưu trữ. Trong OneEntry CMS, điều này đạt được bằng cách tạo các thuộc tính cho dữ liệu. Điều hướng đến Cài đặt và chọn Thuộc tính trong menu ngang. Tạo bộ thuộc tính cho Trang chủ cung cấp tên, điểm đánh dấu và loại: Sau khi tạo, nó sẽ trông giống như trong ảnh chụp màn hình bên dưới: Tương tự, hãy tạo hai bộ thuộc tính riêng biệt cho Quần áo và Trang bị. Sau khi tạo, kết quả sẽ giống như trong ảnh chụp màn hình bên dưới. Bây giờ, hãy xác định các thuộc tính cụ thể cho từng bộ. Dựa trên nội dung chúng tôi đã đưa vào wireframe phần Trang chủ trước đó, chúng tôi muốn hiển thị tiêu đề, mô tả và hình ảnh. Nhấp vào mục thiết bị cho Trang chủ và tạo tên thuộc tính, điểm đánh dấu và loại thuộc tính sau như trong danh sách bên dưới. Bây giờ, hãy quay lại và nhấp vào biểu tượng bánh răng cho Quần áo. Các thuộc tính của trang này sẽ khác một chút vì chúng tôi muốn hiển thị tiêu đề, phụ đề, mô tả, hình ảnh và giá sản phẩm. Đây là cách cấu trúc thuộc tính sẽ trông như thế nào: Tiếp theo, thực hiện tương tự cho trang Gear, trang này sẽ sử dụng cấu trúc tương tự: Thêm nội dung Ở giai đoạn này của dự án, chúng tôi đã xác định cấu trúc nội dung và sẵn sàng bắt đầu tạo nội dung. Điều hướng đến phần Quản lý nội dung nơi trước đây bạn đã tạo tất cả các trang của mình cho trang web: Bấm vào nút chỉnh sửa cho Trang chủ. Sau đó, nhấp vào tab Thuộc tính trên menu Ngang: Chọn Trang chủ cho Tập thuộc tính. Điều đó sẽ tải lên tất cả các thuộc tính mà chúng tôi đã tạo trước đó trong Cài đặt cho Trang chủ. Bây giờ hãy điền một số dữ liệu mẫu mà bạn muốn hiển thị trên Trang chủ. Bây giờ, hãy thêm một số nội dung cho trang Quần áo và Dụng cụ của chúng tôi. Vì chúng tôi đã chọn loại Trang làm Danh mục, hãy chọn Danh mục từ menu bên trái và cả hai trang sẽ hiển thị ở đó: Bây giờ, hãy nhấp vào biểu tượng Thêm cho Quần áo và thêm một vài mặt hàng. Đầu tiên, thêm Tiêu đề cho Sản phẩm bạn muốn thêm. Bây giờ hãy chuyển sang tab Thuộc tính, chọn Quần áo cho Tập hợp thuộc tính và điền dữ liệu được yêu cầu. Quay lại menu Danh mục và một vài mặt hàng khác cho cả Quần áo và Trang bị. Đối với ứng dụng demo của chúng tôi, tôi đã thêm 4 mục như trong ảnh chụp màn hình bên dưới: Tạo mã thông báo truy cập API Tất cả dữ liệu được tạo trong OneEntry CMS đều được bảo vệ, vì vậy chúng tôi sẽ phải tạo mã thông báo riêng để có thể truy cập dữ liệu đó. Để làm điều đó, hãy điều hướng đến Cài đặt và chọn Mã thông báo ứng dụng. Nhập tên ứng dụng và ngày hết hạn rồi nhấp vào Tạo. Điều này sẽ tạo ra một khóa API duy nhất. Nhấp vào biểu tượng xem trong danh sách hành động và bạn sẽ có thể nhìn thấy phím. Sao chép nó vào bảng tạm vì chúng ta sẽ cần nó trong phần tiếp theo của hướng dẫn. Thiết lập dự án NextJS Trong phần hướng dẫn này, chúng ta sẽ bắt đầu làm việc với mã và định cấu hình dự án NextJS để hoạt động với OneEntry CMS. Mở terminal và chạy lệnh . npx create-next-app@latest CLI sẽ khởi động trình hướng dẫn thiết lập. Nhập tên dự án của bạn và chọn tất cả các giá trị mặc định như hiển thị bên dưới: Chờ một phút để hoàn tất quá trình thiết lập và bạn sẽ nhận được thông báo khi ứng dụng NextJS được tạo. Sau đó, thay đổi thư mục thành thư mục mới tạo bằng lệnh , rồi chạy để khởi động máy chủ của nhà phát triển. cd winter-sports npm run dev Để truy cập nó, hãy nhấp vào liên kết được cung cấp trên thiết bị đầu cuối hoặc mở trình duyệt web của bạn và điều hướng đến theo cách thủ công. http://localhost:3000 Bạn sẽ thấy trang đích của máy chủ dành cho nhà phát triển NextJS: Bây giờ, hãy tạo một giá trị môi trường mà chúng ta sẽ cần cho ứng dụng của mình. Chuyển về trình soạn thảo mã của bạn và tạo tệp ở thư mục gốc của dự án. .env Dán khóa API bạn đã sao chép vào bảng nhớ tạm trước đó như sau: API_KEY=your-api-code-from-oneentry Điều này sẽ cho phép chúng tôi truy cập khóa thông qua sau khi chúng tôi thực hiện lệnh gọi API để tìm nạp dữ liệu từ OneEntry CMS. process.env.API_KEY Chúng tôi cũng cần định cấu hình NextJS để nó cho phép chúng tôi đưa phương tiện từ miền bên ngoài vào. Chúng tôi sẽ cần điều này để truy cập hình ảnh từ OneEntry CMS. Mở tệp trong thư mục gốc của dự án và chỉnh sửa nó như sau: next.config.js const nextConfig = { images: { remotePatterns: [ { hostname: "ecommerce.oneentry.cloud", }, ], }, }; module.exports = nextConfig; Cuối cùng, chúng ta sẽ cần đặt lại kiểu mặc định của Tailwind cho ứng dụng vì chúng ta sẽ viết tất cả các kiểu từ đầu. Mở tệp trong thư mục nằm trong thư mục và thay đổi nội dung tệp thành như sau: globals.css app src @tailwind base; @tailwind components; @tailwind utilities; Tạo loại Vì chúng ta sẽ làm việc với TypeScript nên chúng ta sẽ cần xác định loại dữ liệu nào chúng ta sẽ sử dụng trong ứng dụng của mình. Chúng ta có thể thực hiện việc này bên trong các trang và thành phần, nhưng để giữ mã sạch hơn và tránh lặp lại, hãy tạo một thư mục mới bên trong thư mục . Tạo tệp bên trong thư mục mới tạo và bao gồm mã: interfaces app data.tsx export interface Product { id: string; category: string; title: string; subtitle: string; description: string; image: string; price: number; } export interface ProductAPI { id: string; attributeValues: { en_US: { producttitle: { value: { htmlValue: string }[]; }; productsubtitle: { value: { htmlValue: string }[]; }; productdescription: { value: { htmlValue: string }[]; }; productimage: { value: { downloadLink: string }[]; }; productprice: { value: number; }; }; }; } export interface Page { pageUrl: string; title: string; description: string; image: string; localizeInfos: { en_US: { title: string; }; }; } export interface PageAPI { attributeValues: { en_US: { herotitle: { value: { htmlValue: string }[]; }; herodescription: { value: { htmlValue: string }[]; }; heroimage: { value: { downloadLink: string }[]; }; }; }; } export interface URLProps { params: { category: string; productId: string; }; } export interface TextProps { className: string; text: string; } Cả dữ liệu Sản phẩm và Trang đều sẽ có các loại cho cấu trúc dữ liệu hiển thị giao diện người dùng và phản hồi từ API thông qua phương thức tìm nạp. Ngoài ra, chúng tôi đã xác định loại dữ liệu cho dữ liệu từ tham số URL và Trình kết xuất văn bản cho dữ liệu nhận được từ các trường nhập văn bản trong CMS. Tạo hàm tìm nạp API Bây giờ, hãy tạo một số chức năng mà chúng ta sẽ sử dụng để giao tiếp với OneEntry CMS nhằm tìm nạp dữ liệu cho các trang và sản phẩm. Một lần nữa, chúng ta có thể thực hiện việc này trong mỗi tệp, nhưng để giữ mã sạch hơn, hãy tạo một thư mục mới trong thư mục có tệp bên trong nó: services app fetchData.tsx export async function getPages() { const response = await fetch( "https://ecommerce.oneentry.cloud/api/content/pages", { method: "GET", headers: { "x-app-token": `${process.env.API_KEY}`, }, } ); return await response.json(); } export async function getProducts(category: string) { const response = await fetch( `https://ecommerce.oneentry.cloud/api/content/products/page/url/${category}?limit=4&offset=0&langCode=en_US&sortOrder=DESC&sortKey=id`, { method: "GET", headers: { "x-app-token": `${process.env.API_KEY}`, }, } ); return await response.json(); } export async function getProduct(id: string) { const response = await fetch( `https://ecommerce.oneentry.cloud/api/content/products/${id}`, { method: "GET", headers: { "x-app-token": `${process.env.API_KEY}`, }, } ); return await response.json(); } Hàm sẽ tìm nạp dữ liệu về tất cả các trang mà chúng tôi đã tạo trong OneEntry CMS. getPages Hàm sẽ tìm nạp dữ liệu cho một bộ sưu tập sản phẩm cụ thể dựa trên tham số . Chúng ta sẽ truyền tham số khi nhập hàm vào trang sản phẩm. getProducts category Hàm sẽ lấy dữ liệu dựa trên của sản phẩm chúng ta mở. Chúng tôi sẽ chuyển tham số khi nhập hàm vào trang xem trước cho bất kỳ sản phẩm cụ thể nào. getProduct id Lưu ý rằng chúng tôi đã sử dụng để truy cập khóa API mà chúng tôi đã xác định trong tệp trước đó để xác thực quyền truy cập cho OneEntry CMS. process.env.API_KEY .env Tạo hàm trợ giúp Ngoài ra, trong khi chúng ta vẫn ở trong thư mục , hãy tạo một tệp mới khác bên trong nó có tên là , tệp này sẽ bao gồm các hàm tiện ích nhỏ: services helpers.tsx export function calculateTotal(items: { price: number }[]) { return items.reduce((total, item) => total + Number(item.price), 0); } export function boughtStatus(items: { id: string }[], id: string) { return items.some((item) => item.id === id); } export function cartIndex(items: { id: string }[], id: string) { return items.findIndex((item) => item.id === id); } Hàm sẽ cộng giá của các sản phẩm được thêm vào giỏ hàng và trả về tổng giá trị. calculateTotal Đã sẽ phát hiện xem các mặt hàng riêng lẻ trong lộ trình xem trước đã được thêm vào giỏ hàng chưa. boughtStatus sẽ phát hiện vị trí của mặt hàng trong mảng đối với các sản phẩm đã được thêm vào giỏ hàng. cartIndex Tạo thành phần Điều hướng quay lại thư mục và tạo một thư mục mới bên trong nó. app components Mở thư mục mới tạo và bao gồm bảy tệp riêng biệt trong đó: , , , , , , . Header.tsx Footer.tsx Text.tsx Card.tsx Preview.tsx Order.tsx AddToCart.tsx Thành phần tiêu đề Mở tệp và bao gồm mã sau: Header.tsx import Link from "next/link"; import { Page } from "../interfaces/data"; export default function Header({ pages }: { pages: Page[] }) { return ( <div className="flex justify-between items-center mb-10 p-6"> <Link href="/"> <h1 className="text-xl">🏂 Alpine Sports</h1> </Link> <div className="flex space-x-4 list-none"> {pages.map((page, index: number) => ( <Link key={index} href={page.pageUrl === "home" ? "/" : `/${page.pageUrl}`} > {page.localizeInfos.en_US.title} </Link> ))} </div> </div> ); } Đối với tiêu đề, chúng tôi đã hiển thị tên công ty và lặp qua các liên kết điều hướng mà chúng tôi sẽ nhận được từ API sau khi thành phần được nhập vào các trang. Chúng tôi đã tạo bố cục hai cột và đặt cả hai thành phần ở phía đối diện của màn hình theo chiều ngang để đạt được giao diện điều hướng thông thường. Thành phần chân trang Mở tệp và bao gồm đoạn mã sau: Footer.tsx export default function Footer() { return ( <div className="text-center mt-auto p-6"> <h1>Alpine Sports, Inc.</h1> <p>All rights reserved, {new Date().getFullYear()}</p> </div> ); } Ở phần chân trang, chúng tôi đã bao gồm tên mẫu của công ty và các quyền nội dung kèm theo năm hiện tại. Chúng tôi căn giữa nội dung và thêm một số phần đệm. Thành phần văn bản Mở tệp và bao gồm đoạn mã sau: Text.tsx import { TextProps } from "../interfaces/data"; export default function Text({ className, text }: TextProps) { return ( <div className={className} dangerouslySetInnerHTML={{ __html: text }} /> ); } Thành phần Văn bản sẽ hiển thị dữ liệu văn bản mà chúng tôi nhận được từ OneEntry CMS và hiển thị dữ liệu đó đúng cách trong ứng dụng của chúng tôi mà không cần thẻ HTML. Thành phần thẻ Mở tệp và bao gồm mã sau: Card.tsx import Link from "next/link"; import Text from "../components/Text"; import { Product } from "../interfaces/data"; export default function Card({ product }: { product: Product }) { return ( <Link href={`/${product.category}/${product.id}`}> <div className="group relative"> <div className="group-hover:opacity-75 h-80"> <img src={product.image} alt="Product card image" className="h-full w-full object-cover object-center" /> </div> <div className="mt-4 flex justify-between"> <div> <h3 className="text-sm text-gray-700"> <Text className="" text={product.title} /> </h3> <Text className="mt-1 text-sm text-gray-500" text={product.subtitle} /> </div> <p className="text-sm font-medium text-gray-900">${product.price}</p> </div> </div> </Link> ); } Trong thành phần thẻ, chúng tôi hiển thị hình ảnh, tiêu đề, phụ đề và giá cho từng sản phẩm. Chúng tôi sẽ ánh xạ qua tất cả các mục sau khi nó được nhập vào các trang. Hình ảnh sẽ hiển thị ở đầu thẻ, tiếp theo là tiêu đề và mô tả, giá ở phía dưới bên phải của thành phần. Xem trước thành phần Mở tệp và bao gồm đoạn mã sau: Preview.tsx "use-client"; import Image from "next/image"; import Text from "./Text"; import { Product } from "../interfaces/data"; export default function Preview({ children, productItem, }: { children: React.ReactNode; productItem: Product; }) { return ( <div className="flex mx-auto max-w-screen-xl"> <div className="flex-1 flex justify-start items-center"> <Image src={productItem.image} alt="Product preview image" width="450" height="900" /> </div> <div className="flex-1"> <Text className="text-5xl pb-8" text={productItem.title} /> <Text className="text-4xl pb-8 text-gray-700" text={`$${productItem.price}`} /> <Text className="pb-8 text-gray-500 text-justify" text={productItem.description} /> {children} </div> </div> ); } Thành phần xem trước sẽ được sử dụng để hiển thị thêm thông tin về từng sản phẩm khi người dùng nhấp vào sản phẩm đó. Chúng tôi sẽ hiển thị hình ảnh sản phẩm, tiêu đề, giá cả và mô tả. Bố cục sẽ được chia thành 2 cột, trong đó hình ảnh hiển thị ở cột bên trái, còn nội dung còn lại ở bên phải. Thành phần đặt hàng Mở tệp và bao gồm mã sau: Order.tsx "use client"; import { useState, useEffect } from "react"; import Link from "next/link"; import Image from "next/image"; import Text from "./Text"; import { calculateTotal } from "../services/helpers"; import { Product } from "../interfaces/data"; export default function Order() { const [cartItems, setCartItems] = useState<Product[]>([]); useEffect(() => { const storedCartItems = localStorage.getItem("cartItems"); const cartItems = storedCartItems ? JSON.parse(storedCartItems) : []; setCartItems(cartItems); }, []); return ( <div> {cartItems.map((item, index) => ( <div key={index} className="flex items-center border-b border-gray-300 py-2" > <div className="w-20 h-20 mr-12"> <Image src={item.image} alt={item.title} width={80} height={80} /> </div> <div> <Link href={`/${item.category}/${item.id}`} className="text-lg font-semibold" > <Text className="" text={item.title} /> </Link> <Text className="text-gray-600" text={item.subtitle} /> <p className="text-gray-800">Price: ${item.price}</p> </div> </div> ))} <div className="mt-4 text-end"> <h2 className="text-xl font-semibold mb-8"> Total Amount: ${calculateTotal(cartItems)} </h2> <button className="bg-blue-500 hover:bg-blue-700 py-2 px-8 rounded"> Proceed to checkout </button> </div> </div> ); } Thành phần đơn hàng sẽ liệt kê tất cả các mặt hàng mà người dùng đã thêm vào giỏ hàng. Với mỗi mục sẽ hiển thị hình ảnh, tiêu đề, phụ đề và giá cả. Sau khi thành phần được hiển thị, ứng dụng sẽ truy cập tất cả các mặt hàng hiện có trong giỏ hàng, đặt chúng thành biến trạng thái và hiển thị chúng trên màn hình thông qua phương thức . cardItems map Tổng số lượng mục được hiển thị sẽ được tính thông qua hàm mà chúng tôi đã nhập từ tệp . calculateTotal helpers.tsx Thành phần AddToCart Mở tệp và bao gồm mã sau: AddToCart.tsx "use client"; import React, { useState, useEffect } from "react"; import { boughtStatus, cartIndex } from "../services/helpers"; import { Product } from "../interfaces/data"; export default function AddToCart({ category, id, title, subtitle, image, price, }: Product) { const storedCartItems = JSON.parse(localStorage.getItem("cartItems") || "[]"); const isPurchased = boughtStatus(storedCartItems, id); const indexInCart = cartIndex(storedCartItems, id); const [btnState, setBtnState] = useState(false); useEffect(() => { isPurchased && setBtnState(true); }, []); const handleButtonClick = () => { const updatedCartItems = [...storedCartItems]; if (!btnState && !isPurchased) { updatedCartItems.push({ category, id, title, subtitle, image, price }); } else if (isPurchased) { updatedCartItems.splice(indexInCart, 1); } localStorage.setItem("cartItems", JSON.stringify(updatedCartItems)); setBtnState(!btnState); }; return ( <button className={`${ !btnState ? "bg-blue-500 hover:bg-blue-600" : "bg-yellow-300 hover:bg-yellow-400" } py-2 px-8 rounded`} onClick={handleButtonClick} > {!btnState ? "Add to Cart" : "Remove from Cart"} </button> ); } Thành phần addToCart sẽ được hiển thị trên trang xem trước sản phẩm riêng lẻ và sẽ cho phép người dùng thêm sản phẩm vào giỏ hàng. Khi kết xuất, hàm sẽ phát hiện xem sản phẩm đã được thêm vào giỏ hàng trước đó hay chưa. Nếu đó không phải là nút hiển thị, nó sẽ hiển thị "Thêm vào giỏ hàng" nếu không nó sẽ hiển thị "Xóa khỏi giỏ hàng". isPurchased Tính năng click function sẽ thêm hoặc xóa sản phẩm khỏi mảng mặt hàng dựa trên trạng thái trên cho phù hợp. handleButtonClick Tạo trang Cuối cùng, hãy nhập các thành phần mà chúng ta đã tạo trong phần trước của hướng dẫn và tạo logic trang cho ứng dụng. Trang chủ Mở trong thư mục và chỉnh sửa nội dung của nó như sau: page.tsx app import Image from "next/image"; import Header from "./components/Header"; import Text from "./components/Text"; import Footer from "./components/Footer"; import { getPages } from "./services/fetchData"; import { PageAPI } from "./interfaces/data"; export default async function Home() { const pages = await getPages(); const getValues = (el: PageAPI) => { const { herotitle, herodescription, heroimage } = el.attributeValues.en_US; return { title: herotitle.value[0].htmlValue, description: herodescription.value[0].htmlValue, image: heroimage.value[0].downloadLink, }; }; const pageContent = getValues(pages[0]); return ( <div className="flex flex-col min-h-screen"> <Header pages={pages} /> <div className="flex flex-row mx-auto max-w-screen-xl"> <div className="flex-1"> <Text className="text-6xl pb-10 text-gray-900" text={pageContent.title} /> <Text className="text-xl pb-8 text-gray-500 text-justify" text={pageContent.description} /> </div> <div className="flex-1 flex justify-end items-center"> <Image src={pageContent.image} alt="Photo by Karsten Winegeart on Unsplash" width={450} height={900} /> </div> </div> <Footer /> </div> ); } Ở trang Home đầu tiên chúng ta sẽ gọi hàm để lấy dữ liệu cho Header. getPages Sau đó, chúng tôi sử dụng hàm để tìm nạp dữ liệu trang Hero, sau đó biến chúng thành đối tượng để xử lý dễ dàng hơn. getValues pageContent Sau đó, chúng tôi kết xuất các thành phần Đầu trang và Chân trang đã nhập cũng như chuyển các giá trị cần thiết cho tiêu đề, mô tả và hình ảnh Anh hùng. Trang sản phẩm Tạo một thư mục mới trong thư mục và bên trong nó - một tệp . [category] app page.tsx Việc sử dụng tên tệp cụ thể rất quan trọng vì đó là những gì NextJS sử dụng để xử lý các tuyến đường và truy cập các tham số URL. Bao gồm mã sau vào : page.tsx import Header from "../components/Header"; import Footer from "../components/Footer"; import Card from "../components/Card"; import { getPages, getProducts } from "../services/fetchData"; import { ProductAPI, URLProps } from "../interfaces/data"; export default async function Product({ params }: URLProps) { const { category } = params; const pages = await getPages(); const products = await getProducts(category); const getValues = (products: ProductAPI[]) => { return products.map((el) => { const { producttitle, productsubtitle, productdescription, productimage, productprice, } = el.attributeValues.en_US; return { id: el.id, category: category, title: producttitle.value[0].htmlValue, subtitle: productsubtitle.value[0].htmlValue, description: productdescription.value[0].htmlValue, image: productimage.value[0].downloadLink, price: productprice.value, }; }); }; const productItems = getValues(products.items); return ( <div className="flex flex-col min-h-screen"> <Header pages={pages} /> <div className="mx-auto max-w-screen-xl px-8"> <h2 className="text-4xl text-gray-900 mb-12"> Browse our {category} collection: </h2> <div className="grid gap-x-6 gap-y-10 grid-cols-4 mt-6"> {productItems.map((product) => { return <Card key={product.id} product={product} />; })} </div> </div> <Footer /> </div> ); } Đối với trang sản phẩm, trước tiên chúng tôi lấy tham số từ URL, sau đó chúng tôi chuyển vào hàm , để mô tả danh mục sản phẩm nào chúng tôi cần tìm nạp dựa trên trang nào của trang web được truy cập. category getProducts Sau khi nhận được dữ liệu, chúng tôi tạo một mảng đối tượng bao gồm tất cả các thuộc tính cần thiết để trang xử lý dễ dàng hơn. productItems Sau đó, chúng tôi lặp qua nó thông qua phương thức và hiển thị nó ra màn hình bằng cách chuyển các đạo cụ cho thành phần Thẻ mà chúng tôi đã nhập từ thư mục . map component Trang xem trước Bên trong thư mục , tạo một thư mục khác tên là . [category] [productId] Mở thư mục mới tạo và tạo tệp bên trong nó bằng mã: page.tsx import Header from "../../components/Header"; import Preview from "../../components/Preview"; import AddToCart from "../../components/AddToCart"; import Footer from "../../components/Footer"; import { getPages, getProduct } from "../../services/fetchData"; import { ProductAPI, URLProps } from "../../interfaces/data"; export default async function Product({ params }: URLProps) { const { category, productId } = params; const pages = await getPages(); const product = await getProduct(productId); const getValues = (el: ProductAPI) => { const { producttitle, productsubtitle, productdescription, productimage, productprice, } = el.attributeValues.en_US; return { id: el.id, category: category, title: producttitle.value[0].htmlValue, subtitle: productsubtitle.value[0].htmlValue, description: productdescription.value[0].htmlValue, image: productimage.value[0].downloadLink, price: productprice.value, }; }; const productItem = getValues(product); return ( <div className="flex flex-col min-h-screen"> <Header pages={pages} /> <div className="flex mx-auto max-w-screen-xl"> <div className="flex-1 flex justify-start items-center"> <Preview productItem={productItem}> <AddToCart id={productId} category={category} title={productItem.title} subtitle={productItem.subtitle} description={productItem.description} image={productItem.image} price={productItem.price} /> </Preview> </div> </div> <Footer /> </div> ); } Trang này sẽ cho phép người dùng xem thêm chi tiết về từng sản phẩm riêng lẻ khi họ nhấp vào thẻ của họ trên trang sản phẩm. Trước tiên, chúng tôi lấy tham số từ URL, sau đó chúng tôi chuyển tham số này vào hàm để chỉ định sản phẩm nào chúng tôi cần tìm nạp dựa trên sản phẩm nào được xem trên trang xem trước. productId getProduct Sau khi nhận được dữ liệu, chúng ta tạo một đối tượng bao gồm tất cả các thuộc tính cần thiết để được chuyển vào thành phần Xem trước dưới dạng đạo cụ. productItem Chúng tôi cũng nhận được tham số vì chúng tôi cần chuyển nó đến thành phần Thêm vào giỏ hàng để có thể tạo liên kết hợp lệ cho mặt hàng trong trang Giỏ hàng. category Trang giỏ hàng Cuối cùng, tạo một hàng mới trong thư mục . cart app Mở nó, tạo một tệp mới bên trong nó với đoạn mã sau: page.tsx import Header from "../components/Header"; import Order from "../components/Order"; import Footer from "../components/Footer"; import { getPages } from "../services/fetchData"; export default async function Cart() { const pages = await getPages(); return ( <div className="flex flex-col min-h-screen"> <Header pages={pages} /> <div className="container mx-auto max-w-screen-xl px-8"> <h2 className="text-4xl text-gray-900 mb-12">Shopping cart summary:</h2> <Order /> </div> <Footer /> </div> ); } Trước tiên, chúng tôi tìm nạp dữ liệu cần thiết và sau đó chuyển nó vào Tiêu đề làm đạo cụ. Sau đó, chúng tôi kết xuất thành phần Header với điều hướng, thành phần Order sẽ liệt kê tất cả các mặt hàng mà người dùng đã thêm vào giỏ hàng và cả thành phần Footer với tên công ty và thông tin bản quyền. Kiểm tra Xin chúc mừng, bạn đã thực hiện một dự án làm việc! Trước tiên, hãy kiểm tra xem máy chủ của nhà phát triển có còn chạy không. Nếu không, hãy chạy lệnh để khởi động lại và truy cập để xem. npm run dev localhost:3000 Dự án của bạn bây giờ sẽ trông như thế này: Như bạn có thể thấy, nội dung phần Trang chủ đã được tìm nạp thành công từ bộ thuộc tính Trang chủ mà chúng tôi đã chỉ định trong các trường dữ liệu. Ngoài ra, tất cả các mục từ danh mục OneEntry CMS đã được tìm nạp trong phần Quần áo và Thiết bị với tất cả thông tin được hiển thị chính xác. Người dùng cũng có thể xem trước từng sản phẩm riêng biệt trên trang dành riêng cho nó, nhờ vào các thông số sản phẩm và xử lý lộ trình NextJS. Ngoài ra, tất cả các chức năng và sự kiện đều hoạt động như mong đợi và người dùng có thể thêm và xóa các mặt hàng khỏi giỏ hàng, với tổng số tiền được tính toán. Phần kết luận Trong hướng dẫn này, chúng tôi đã tạo một dự án Thương mại điện tử cho phép người dùng tạo, cập nhật và xóa các trang web cũng như nội dung của chúng cũng như dễ dàng quản lý sản phẩm bằng giao diện danh mục dễ sử dụng nhờ . OneEntry CMS Mã này có sẵn trên , vì vậy hãy thoải mái sao chép nó và thêm nhiều chức năng hơn cho phù hợp với nhu cầu của bạn. Bạn có thể thêm nhiều phần menu hơn vào đó, mở rộng các thành phần riêng lẻ hoặc thậm chí thêm nhiều thành phần hơn để triển khai các tính năng mới. GitHub Hy vọng rằng điều này sẽ hữu ích với bạn và bạn hiểu rõ hơn về cách sử dụng OneEntry CMS làm giải pháp phụ trợ, cách ghép nối nó với giao diện người dùng của ứng dụng và cách sử dụng các tính năng tốt nhất của NextJS, Typescript và Tailwind . Đảm bảo nhận được các tài nguyên, công cụ, mẹo tăng năng suất và mẹo phát triển nghề nghiệp tốt nhất mà tôi khám phá được bằng cách đăng ký ! nhận bản tin của mình Ngoài ra, hãy kết nối với tôi trên , và ! Twitter LinkedIn GitHub Cũng được xuất bản ở đây