paint-brush
Cách sử dụng NextJS, TypeScript, Tailwind và OneEntry CMS để xây dựng cửa hàng thương mại điện tử 🛒👨‍💻từ tác giả@madzadev
1,548 lượt đọc
1,548 lượt đọc

Cách sử dụng NextJS, TypeScript, Tailwind và OneEntry CMS để xây dựng cửa hàng thương mại điện tử 🛒👨‍💻

từ tác giả Madza2024/01/24
Read on Terminal Reader
Read this story w/o Javascript

dài quá đọc không nổi

Chúng tôi sẽ xây dựng một dự án Thương mại điện tử thực tế để xem NextJS, TypeScript, Tailwind CSS và OneEntry CMS phối hợp với nhau như thế nào cũng như cách chúng có thể được sử dụng để đơn giản hóa việc quản lý nội dung.
featured image - Cách sử dụng NextJS, TypeScript, Tailwind và OneEntry CMS để xây dựng cửa hàng thương mại điện tử 🛒👨‍💻
Madza HackerNoon profile picture
0-item

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ệ

NextJS là 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.


TypeScript 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.


Tailwind CSS 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.


OneEntry CMS 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.

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.


Trang chủ cửa hàng thương mại điện tử


Phần cửa hàng đầu tiên sẽ dành riêng cho Quần áo.


Trang sản phẩm của cửa hàng thương mại điện tử


Phần cửa hàng thứ hai sẽ bao gồm Gear.


Trang sản phẩm cửa hàng thương mại điện tử số 2


Mỗi mục sẽ có một trang Xem trước riêng với thông tin chi tiết.


Trang xem trước sản phẩm của cửa hàng thương mại điện tử


Các mặt hàng đã có trong giỏ hàng sẽ có tùy chọn để loại bỏ chúng.


Trang xem trước sản phẩm cửa hàng thương mại điện tử số 2


Giỏ hàng sẽ liệt kê tất cả các mặt hàng đã chọn và tính tổng.


Trang giỏ hàng thương mại điện tử

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à đăng ký qua tài khoản email của bạn.


Trang đăng ký OneEntry


Sau đó, đăng nhập và bạn sẽ được chuyển đến bảng điều khiển OneEntry.


Bắt đầu bằng cách tạo một dự án mới.


Trang bảng điều khiển OneEntry


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.


Trang dự án OneEntry


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.


Trang bảng điều khiển OneEntry

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.


Trang đăng nhập OneEntry CMS


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.


OneEntry CMS - chỉnh sửa một trang


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.


Danh sách các trang CMS của OneEntry

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:


Các loại trang CMS OneEntry


Sau khi tạo, nó sẽ trông giống như trong ảnh chụp màn hình bên dưới:


Thuộc tính trang OneEntry CMS


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.


Thuộc tính CMS OneEntry #2


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.


Cài đặt trang OneEntry CMS


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:


Thuộc tính trang OneEntry CMS


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ự:


Thuộc tính trang OneEntry CMS #2

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:


Danh sách các trang CMS của OneEntry


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:


Thuộc tính chọn OneEntry CMS


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ủ.


Tạo nội dung CMS OneEntry


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ị ở đó:


Danh mục CMS OneEntry


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.


Tiêu đề danh mục CMS OneEntry


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.


OneEntry CMS thêm sản phẩm


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:


Danh sách sản phẩm CMS OneEntry

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.


OneEntry CMS - tạo mã thông báo ứng dụng


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.


OneEntry CMS - sao chép mã thông báo ứng dụng

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:


Trình hướng dẫn thiết lập NextJS trong thiết bị đầu cuố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 cd winter-sports , rồi chạy npm run dev để khởi động máy chủ của nhà phát triển.


Để 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 http://localhost:3000 theo cách thủ công.


Bạn sẽ thấy trang đích của máy chủ dành cho nhà phát triển NextJS:


Bản xem trước máy chủ của 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 .env ở thư mục gốc của dự án.


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 process.env.API_KEY 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.


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 next.config.js trong thư mục gốc của dự án và chỉnh sửa nó như sau:


 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 globals.css trong thư mục app nằm trong thư mục src và thay đổi nội dung tệp thành như sau:


 @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 interfaces bên trong thư mục app . Tạo tệp data.tsx bên trong thư mục mới tạo và bao gồm mã:


 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 services trong thư mục app có tệp fetchData.tsx bên trong nó:


 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 getPages 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.


Hàm getProducts 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ố category . Chúng ta sẽ truyền tham số khi nhập hàm vào trang sản phẩm.


Hàm getProduct sẽ lấy dữ liệu dựa trên id 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.


Lưu ý rằng chúng tôi đã sử dụng process.env.API_KEY để truy cập khóa API mà chúng tôi đã xác định trong tệp .env trước đó để xác thực quyền truy cập cho OneEntry CMS.

Tạo hàm trợ giúp

Ngoài ra, trong khi chúng ta vẫn ở trong thư mục services , hãy tạo một tệp mới khác bên trong nó có tên là helpers.tsx , tệp này sẽ bao gồm các hàm tiện ích nhỏ:


 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 calculateTotal 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ị.


Đã boughtStatus 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.


cartIndex 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.

Tạo thành phần

Điều hướng quay lại thư mục app và tạo một thư mục mới components bên trong nó.


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 Header.tsx và bao gồm mã sau:


 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 Footer.tsx và bao gồm đoạn mã sau:


 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 Text.tsx và bao gồm đoạn mã sau:


 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 Card.tsx và bao gồm mã sau:


 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 Preview.tsx và bao gồm đoạn mã sau:


 "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 Order.tsx và bao gồm mã sau:


 "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 cardItems và hiển thị chúng trên màn hình thông qua phương thức map .


Tổng số lượng mục được hiển thị sẽ được tính thông qua hàm calculateTotal mà chúng tôi đã nhập từ tệp helpers.tsx .


Thành phần AddToCart

Mở tệp AddToCart.tsx và bao gồm mã sau:


 "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 isPurchased 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".


Tính năng click function handleButtonClick 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.

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ở page.tsx trong thư mục app và chỉnh sửa nội dung của nó như sau:


 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 getPages để lấy dữ liệu cho Header.


Sau đó, chúng tôi sử dụng hàm getValues để tìm nạp dữ liệu trang Hero, sau đó biến chúng thành đối tượng pageContent để xử lý dễ dàng hơn.


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 [category] trong thư mục app và bên trong nó - một tệp 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ố category từ URL, sau đó chúng tôi chuyển vào hàm getProducts , để 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.


Sau khi nhận được dữ liệu, chúng tôi tạo một mảng đối tượng productItems bao gồm tất cả các thuộc tính cần thiết để trang xử lý dễ dàng hơn.


Sau đó, chúng tôi lặp qua nó thông qua phương thức map 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 component .


Trang xem trước

Bên trong thư mục [category] , tạo một thư mục khác tên là [productId] .


Mở thư mục mới tạo và tạo tệp page.tsx bên trong nó bằng mã:


 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ố productId từ URL, sau đó chúng tôi chuyển tham số này vào hàm getProduct để 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.


Sau khi nhận được dữ liệu, chúng ta tạo một đối tượng productItem 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ụ.


Chúng tôi cũng nhận được tham số category 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.


Trang giỏ hàng

Cuối cùng, tạo một cart hàng mới trong thư mục app .


Mở nó, tạo một tệp mới page.tsx bên trong nó với đoạn mã sau:


 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 npm run dev để khởi động lại và truy cập localhost:3000 để xem.


Dự án của bạn bây giờ sẽ trông như thế này:


Hướng dẫn dự án cuối cùng


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 GitHub , 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.


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 Twitter , LinkedInGitHub !


Cũng được xuất bản ở đây