Đâ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 .
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.
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.
Đầ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.
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.
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.
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.
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ự:
Ở 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ấ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.
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 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â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;
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.
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.
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.
Đ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.
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.
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:
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.
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 , LinkedIn và GitHub !
Cũng được xuất bản ở đây