重构Hero组件Category组件Banner组件

This commit is contained in:
jingrow 2025-06-23 20:01:24 +08:00
parent fb3bb322ac
commit a15a7e0c18
8 changed files with 103 additions and 148 deletions

View File

@ -12,8 +12,7 @@ const LoadingSpinner = () => (
</div> </div>
); );
export const revalidate = 10; export const revalidate = Number(process.env.JSITE_REVALIDATE_SECONDS) || 3600;
export const dynamicParams = true;
export async function generateStaticParams() { export async function generateStaticParams() {
const slugs = await getAllSlugs(); const slugs = await getAllSlugs();

View File

@ -4,7 +4,7 @@ import CategoryItems from "@/components/homes/home-15/CategoryItems";
import React from "react"; import React from "react";
import { getSiteSettings } from "@/utils/siteSettings"; import { getSiteSettings } from "@/utils/siteSettings";
export const revalidate = 3600; export const revalidate = Number(process.env.JSITE_REVALIDATE_SECONDS) || 3600;
export async function generateMetadata() { export async function generateMetadata() {
const siteUrl = process.env.SITE_URL; const siteUrl = process.env.SITE_URL;
@ -36,7 +36,7 @@ export default function Page() {
<Hero /> <Hero />
</section> </section>
<section className="w-full xl:w-10/12 xl:mx-auto overflow-x-hidden"> <section className="w-full xl:w-10/12 xl:mx-auto overflow-x-hidden">
<CategoryItems /> <SwiperItems />
</section> </section>
</> </>
</div>{" "} </div>{" "}

View File

@ -1,35 +1,13 @@
"use client"; import { fetchComponentData, downloadToLocal } from "@/utils/data";
import React, { useEffect, useState } from "react";
import Image from "next/image";
import axios from "axios";
export default function Banner({ componentName = "Banner", className = "" }) { export default async function Banner({ componentName = "Banner", className = "" }) {
const [data, setData] = useState(null); const res = await fetchComponentData(componentName);
const [loading, setLoading] = useState(true); const data = res.data;
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
const res = await axios.get("/api/get-component-data", {
params: { component_name: componentName },
});
setData(res.data.data);
} catch (err) {
setError(`Failed to fetch ${componentName} data`);
} finally {
setLoading(false);
}
}
fetchData();
}, [componentName]);
if (loading) return null;
if (error) return null;
if (!data) return null; if (!data) return null;
const bannerImg = data.image; //
const bannerImg = data.image ? await downloadToLocal(data.image) : "";
return ( return (
<section <section
@ -38,7 +16,7 @@ export default function Banner({ componentName = "Banner", className = "" }) {
> >
<div className="container pt-36 xl:pt-[12.5rem] lg:pt-[12.5rem] md:pt-[12.5rem] pb-32 xl:pb-40 lg:pb-40 md:pb-40 !text-center"> <div className="container pt-36 xl:pt-[12.5rem] lg:pt-[12.5rem] md:pt-[12.5rem] pb-32 xl:pb-40 lg:pb-40 md:pb-40 !text-center">
<div className="flex flex-wrap mx-[-15px]"> <div className="flex flex-wrap mx-[-15px]">
<div className="md:w-10/12 lg:w-8/12 xl:w-7/12 xxl:w-6/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto"> <div className="md:w-10/12 lg:w-8/12 xl:w-7/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto">
<h1 className="!text-[calc(1.365rem_+_1.38vw)] font-bold !leading-[1.2] xl:!text-[2.4rem] !text-white !mb-3"> <h1 className="!text-[calc(1.365rem_+_1.38vw)] font-bold !leading-[1.2] xl:!text-[2.4rem] !text-white !mb-3">
{data.title} {data.title}
</h1> </h1>

View File

@ -1,37 +1,12 @@
"use client"; "use client";
import React, { useEffect, useState } from "react";
import Image from "next/image"; import Image from "next/image";
import AnimatedText from "@/components/common/AnimatedText"; import AnimatedText from "@/components/common/AnimatedText";
import Link from "next/link"; import Link from "next/link";
import axios from "axios";
export default function Hero() { export default function HeroUI({ data }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
const res = await axios.get("/api/get-component-data", {
params: { component_name: "Hero" },
});
setData(res.data.data);
} catch (err) {
setError("获取Features数据失败");
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) return <div className="text-center py-20">加载中...</div>;
if (error) return null;
if (!data) return null; if (!data) return null;
const bgImage = data.bg_image || "/assets/img/photos/blurry.png"; const bgImage = data.image || "/assets/img/photos/blurry.png";
const title = data.title || ""; const title = data.title || "";
const subtitle = data.subtitle || ""; const subtitle = data.subtitle || "";
const buttonText = data.button_text || "MORE"; const buttonText = data.button_text || "MORE";
@ -39,7 +14,6 @@ export default function Hero() {
const buttonText2 = data.button_2_text || "MORE"; const buttonText2 = data.button_2_text || "MORE";
const buttonLink2 = data.button_2_link || "#"; const buttonLink2 = data.button_2_link || "#";
return ( return (
<div className="container pt-36 xl:py-[12.5rem] lg:py-[12.5rem] md:py-[12.5rem] !text-center !relative"> <div className="container pt-36 xl:py-[12.5rem] lg:py-[12.5rem] md:py-[12.5rem] !text-center !relative">
<div <div

View File

@ -0,0 +1,13 @@
import { fetchComponentData } from "@/utils/data";
import HeroUI from "./HeroUI";
export default async function Hero() {
const result = await fetchComponentData("Hero");
if (result.error || !result.data) {
if(result.error) console.error("Failed to fetch Hero data:", result.error);
return null;
}
return <HeroUI data={result.data} />;
}

View File

@ -1,88 +1,52 @@
"use client"; import { fetchComponentData, fetchCategoryData } from "@/utils/data";
import React, { useEffect, useState } from "react";
import axios from "axios";
/** /**
* Product category sidebar component * Product category sidebar component
* @param {string} componentName - component name * @param {string} componentName - component name
* @param {string} className - additional class names * @param {string} className - additional class names
*/ */
export default function Category({ export default async function Category({
componentName = "Category", componentName = "Category",
className = "", className = "",
}) { }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [activeCategory, setActiveCategory] = useState(null);
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
// 1. // 1.
const metaRes = await axios.get("/api/get-component-data", { const metaRes = await fetchComponentData(componentName);
params: { component_name: componentName }, const metaData = metaRes.data;
});
const metaData = metaRes.data.data || {}; // componentName
if (!metaData || !metaData.p1 || !metaData.p2) {
return null;
}
// 2. // 2.
if (metaData.p1 && metaData.p2) { let data = {
const catRes = await axios.get("/api/get-category", {
params: { pagetype: metaData.p1, name: metaData.p2 },
});
const categoryData = catRes.data.message?.data || {};
setData({
title: metaData.title || "Product Categories", title: metaData.title || "Product Categories",
subtitle: metaData.subtitle || "", subtitle: metaData.subtitle || "",
slug: "",
items: [],
};
const catRes = await fetchCategoryData({ pagetype: metaData.p1, name: metaData.p2 });
const categoryData = catRes.data || {};
data = {
...data,
slug: categoryData.slug, slug: categoryData.slug,
items: categoryData.children || [], items: categoryData.children || [],
});
}
} catch (err) {
console.error("获取分类数据失败:", err);
setError("Failed to fetch category data");
} finally {
setLoading(false);
}
}
fetchData();
}, [componentName]);
if (loading) return null;
if (error) return null;
if (!data) return null;
const handleCategoryClick = (categorySlug) => {
if (activeCategory === categorySlug) {
setActiveCategory(null);
} else {
setActiveCategory(categorySlug);
}
}; };
// //
const renderCategories = (items, parentSlugs = []) => { const renderCategories = (items, parentSlugs = []) => {
if (!items || !items.length) return null; if (!items || !items.length) return null;
return ( return (
<ul className="pl-0 list-none"> <ul className="pl-0 list-none">
{items.map((item) => { {items.map((item) => {
const hasChildren = Array.isArray(item.children) && item.children.length > 0; const hasChildren = Array.isArray(item.children) && item.children.length > 0;
const isActive = activeCategory === item.slug;
const currentPath = [...parentSlugs, item.slug]; const currentPath = [...parentSlugs, item.slug];
return ( return (
<li key={item.slug} className="nav-item"> <li key={item.slug} className="nav-item">
<div <div className="flex items-center justify-between cursor-pointer py-2">
className="flex items-center justify-between cursor-pointer py-2"
onClick={() => {
if (hasChildren) handleCategoryClick(item.slug);
}}
>
<a <a
className="!text-[#60697b] hover:!text-[#1fc76f]" className="!text-[#60697b] hover:!text-[#1fc76f]"
href={`/${currentPath.join('/')}`} href={`/${currentPath.join('/')}`}
onClick={e => e.stopPropagation()} // a
> >
{item.name} {item.name}
</a> </a>
@ -90,8 +54,7 @@ export default function Category({
<span className="ml-2 text-gray-400 hover:text-[#1fc76f]">{'>'}</span> <span className="ml-2 text-gray-400 hover:text-[#1fc76f]">{'>'}</span>
)} )}
</div> </div>
{hasChildren && (
{hasChildren && isActive && (
<div className="pl-4 sub-category"> <div className="pl-4 sub-category">
<ul className="list-none pl-0"> <ul className="list-none pl-0">
{item.children.map((child) => ( {item.children.map((child) => (
@ -99,7 +62,6 @@ export default function Category({
<a <a
className="!text-[#60697b] hover:!text-[#1fc76f] text-sm" className="!text-[#60697b] hover:!text-[#1fc76f] text-sm"
href={`/${currentPath.join('/')}/${child.slug}`} href={`/${currentPath.join('/')}/${child.slug}`}
onClick={e => e.stopPropagation()}
> >
{child.name} {child.name}
</a> </a>

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -9,7 +9,7 @@ if (!fs.existsSync(PUBLIC_FILES_DIR)) {
fs.mkdirSync(PUBLIC_FILES_DIR, { recursive: true }); fs.mkdirSync(PUBLIC_FILES_DIR, { recursive: true });
} }
async function downloadToLocal(fileUrl) { export async function downloadToLocal(fileUrl) {
if (!fileUrl) return fileUrl; if (!fileUrl) return fileUrl;
try { try {
let fullUrl = fileUrl; let fullUrl = fileUrl;
@ -51,7 +51,7 @@ function extractImageUrlsFromHtml(html) {
return urls; return urls;
} }
async function processDataItem(item, downloadFiles) { export async function processDataItem(item, downloadFiles) {
if (!downloadFiles) return item; if (!downloadFiles) return item;
if (item.image) { if (item.image) {
@ -174,38 +174,67 @@ export async function getAllSlugs() {
export async function fetchComponentData(componentName) { export async function fetchComponentData(componentName) {
try { try {
// We use no-store to ensure data is fresh, suitable for dynamic component settings. const res = await axios.get(
// For more static data, you might adjust the cache policy. `${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_component_data`,
const res = await fetch(`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_component_data?component_name=${componentName}`, { cache: 'no-store' }); {
if (!res.ok) { params: { component_name: componentName },
const errorText = await res.text(); }
console.error(`Failed to fetch component data for ${componentName}: ${res.statusText}`, errorText); );
return { data: res.data.message?.data || null };
} catch (error) {
if (
error.response &&
(error.response.status === 403 || error.response.status === 404)
) {
return { data: null }; return { data: null };
} }
const result = await res.json(); console.error(
return { data: result.message?.data || null }; `Error fetching component data for ${componentName}:`,
} catch (error) { error.message
console.error(`Error fetching component data for ${componentName}:`, error); );
return { data: null }; return { data: null };
} }
} }
export async function fetchListViewData({ pagetype, category, count }) { export async function fetchListViewData({ pagetype, category, count }) {
try { try {
const queryParams = new URLSearchParams({ pagetype }); const params = { pagetype };
if (category) queryParams.append("category", category); if (category) params.category = category;
if (count !== undefined && count !== null) queryParams.append("count", String(count)); if (count !== undefined && count !== null) params.count = String(count);
const res = await fetch(`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_listview_data?${queryParams.toString()}`, { cache: 'no-store' }); const res = await axios.get(
if (!res.ok) { `${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_listview_data`,
const errorText = await res.text(); { params }
console.error(`Failed to fetch list view data: ${res.statusText}`, errorText); );
return { data: [] };
} return { data: res.data.message?.data || [] };
const result = await res.json();
return { data: result.message?.data || [] };
} catch (error) { } catch (error) {
console.error(`Error fetching list view data:`, error); if (
error.response &&
(error.response.status === 403 || error.response.status === 404)
) {
return { data: [] };
}
console.error(`Error fetching list view data:`, error.message);
return { data: [] }; return { data: [] };
} }
} }
export async function fetchCategoryData({ pagetype, name }) {
try {
const res = await axios.get(
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_category`,
{ params: { pagetype, name } }
);
return { data: res.data.message?.data || null };
} catch (error) {
if (
error.response &&
(error.response.status === 403 || error.response.status === 404)
) {
return { data: null };
}
console.error(`Error fetching category data:`, error.message);
return { data: null };
}
}