重构Hero组件Category组件Banner组件
This commit is contained in:
parent
fb3bb322ac
commit
a15a7e0c18
@ -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();
|
||||||
|
|||||||
@ -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>{" "}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
13
components/homes/home-15/Hero/index.jsx
Normal file
13
components/homes/home-15/Hero/index.jsx
Normal 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} />;
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
|||||||
BIN
public/files/banner-about.jpg
Normal file
BIN
public/files/banner-about.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
@ -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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user