美化header布局及增加hero组件

This commit is contained in:
jingrow 2025-06-17 21:41:09 +08:00
parent 80e0943b2e
commit 6c599eafac
7 changed files with 186 additions and 154 deletions

View File

@ -34,23 +34,15 @@ export default function Page() {
<> <>
<div className="grow shrink-0"> <div className="grow shrink-0">
<> <>
<section className="wrapper bg-[#21262c] opacity-100"> <section className="wrapper overflow-hidden">
<Hero /> <Hero />
{/* /.swiper-container */} {/* /.swiper-container */}
</section> </section>
<section className="wrapper !bg-[#ffffff] "> <section className="wrapper !bg-[#ffffff] ">
<div className="container py-[4.5rem] xl:!py-24 lg:!py-24 md:!py-24"> <div className="container py-[4.5rem] xl:!py-24 lg:!py-24 md:!py-24">
<Features /> <Features />
<Process />
{/*/.row */}
</div> </div>
{/* /.container */}
</section> </section>
{/* /section */}
{/* Gallery 单独渲染,保证背景色全屏 */}
<Gallery />
<Faqs />
</> </>
</div>{" "} </div>{" "}
</> </>

View File

@ -9,33 +9,27 @@ const stringsDefault = [
]; ];
const AnimatedText = ({ strings = stringsDefault }) => { const AnimatedText = ({ strings = stringsDefault }) => {
const [currentStr, setCurrentStr] = useState(strings[0]); //
const filteredStrings = strings.filter(str => !!str && str.trim() !== "");
const [currentIndex, setCurrentIndex] = useState(0);
const [animatedText, setAnimatedText] = useState(true); const [animatedText, setAnimatedText] = useState(true);
useEffect(() => { useEffect(() => {
if (filteredStrings.length === 0) return;
const interval = setInterval(() => { const interval = setInterval(() => {
setAnimatedText(false); setAnimatedText(false);
setTimeout(() => { setTimeout(() => {
setAnimatedText(true); setAnimatedText(true);
}, 200); }, 200);
setCurrentIndex((prevIndex) => (prevIndex + 1) % filteredStrings.length);
setCurrentStr((prevStr) => { }, 5000);
if (prevStr === strings[0]) {
return strings[1];
} else if (prevStr === strings[1]) {
return strings[2];
} else {
return strings[0];
}
});
}, 2000);
return () => clearInterval(interval); // Cleanup interval on component unmount return () => clearInterval(interval); // Cleanup interval on component unmount
}, []); }, [filteredStrings]);
if (filteredStrings.length === 0) return null;
return ( return (
<> <>
{" "}
{animatedText ? ( {animatedText ? (
<span <span
className={`animate__animated animate__fadeInDown`} className={`animate__animated animate__fadeInDown`}
@ -44,7 +38,7 @@ const AnimatedText = ({ strings = stringsDefault }) => {
visibility: "visible", visibility: "visible",
}} }}
> >
{currentStr} {filteredStrings[currentIndex]}
</span> </span>
) : ( ) : (
<span <span
@ -54,7 +48,7 @@ const AnimatedText = ({ strings = stringsDefault }) => {
visibility: "hidden", visibility: "hidden",
}} }}
> >
{currentStr} {filteredStrings[currentIndex]}
</span> </span>
)} )}
</> </>

View File

@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
import Nav from "./Nav"; import Nav from "./Nav";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import LanguageSelect from "./LanguageSelect"; import LoginButton from "./LoginButton";
import SocialLinks from "../contact/SocialLinks"; import SocialLinks from "../contact/SocialLinks";
import { getSiteSettings } from "@/utlis/siteSettings"; import { getSiteSettings } from "@/utlis/siteSettings";
@ -20,7 +20,7 @@ export default function Header15() {
const email = siteSettings.email; const email = siteSettings.email;
return ( return (
<header className="relative w-full !bg-[#f6f3f9]"> <header className="relative w-full !bg-[#fefefe]">
<nav className="navbar navbar-expand-lg transparent navbar-light w-full"> <nav className="navbar navbar-expand-lg transparent navbar-light w-full">
<div className="w-full flex flex-row flex-nowrap items-center px-4"> <div className="w-full flex flex-row flex-nowrap items-center px-4">
<div className="navbar-brand"> <div className="navbar-brand">
@ -73,7 +73,7 @@ export default function Header15() {
<div className="navbar-other w-full !flex !ml-auto"> <div className="navbar-other w-full !flex !ml-auto">
<ul className="navbar-nav !flex-row !items-center !ml-auto"> <ul className="navbar-nav !flex-row !items-center !ml-auto">
<li className="nav-item dropdown language-select uppercase group"> <li className="nav-item dropdown language-select uppercase group">
<LanguageSelect color="#22b573" /> <LoginButton color="#22b573" />
</li> </li>
<li className="nav-item xl:!hidden lg:!hidden"> <li className="nav-item xl:!hidden lg:!hidden">
<button className="hamburger offcanvas-nav-btn"> <button className="hamburger offcanvas-nav-btn">

View File

@ -9,7 +9,7 @@ export default function LanguageSelect({ color = "#22b573" }) {
<> <>
{" "} {" "}
<a <a
className={`nav-link dropdown-item dropdown-toggle hover:!text-[${color}] after:!text-[${color}]`} className={`nav-link dropdown-item dropdown-toggle !text-[.7rem] hover:!text-[${color}] after:!text-[${color}]`}
href="#" href="#"
role="button" role="button"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
@ -24,7 +24,7 @@ export default function LanguageSelect({ color = "#22b573" }) {
<a <a
href="#" href="#"
onClick={() => setActiveLang(lang)} onClick={() => setActiveLang(lang)}
className={`dropdown-item hover:!text-[${color}] hover:bg-[inherit] ${ className={`dropdown-item !text-[.7rem] hover:!text-[${color}] hover:bg-[inherit] ${
activeLang === lang ? `!text-[${color}]` : "" activeLang === lang ? `!text-[${color}]` : ""
}`} }`}
> >

View File

@ -0,0 +1,11 @@
"use client";
export default function LoginButton() {
return (
<a
href="https://cloud.jingrow.com/dashboard/login"
className="btn btn-ash !text-[.7rem] !text-[#333333] hover:!border-[#333333] focus:shadow-[rgba(134,140,151,1)] !rounded-[50rem] !mb-2 !mr-[.25rem]">
登录
</a>
);
}

View File

@ -85,7 +85,7 @@ export default function Nav({ color = "#fab758" }) {
{/* 如果有href左侧为可点击跳转始终用当前item的href */} {/* 如果有href左侧为可点击跳转始终用当前item的href */}
{hasHref && ( {hasHref && (
<Link <Link
className={`${level > 0 ? "dropdown-item" : "nav-link"} !text-[.85rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${pathname === item.href ? "!text-[var(--current-color)]" : ""}`} className={`${level > 0 ? "dropdown-item" : "nav-link"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${pathname === item.href ? "!text-[var(--current-color)]" : ""}`}
href={item.href} href={item.href}
style={{ paddingRight: 0 }} style={{ paddingRight: 0 }}
> >
@ -94,7 +94,7 @@ export default function Nav({ color = "#fab758" }) {
)} )}
{/* 右侧小三角,负责展开下拉 */} {/* 右侧小三角,负责展开下拉 */}
<a <a
className={`${hasHref ? "dropdown-toggle" : (level > 0 ? "dropdown-item" : "nav-link") + " dropdown-toggle"} !text-[.85rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${!hasHref && pathname === item.href ? "!text-[var(--current-color)]" : ""}`} className={`${hasHref ? "dropdown-toggle" : (level > 0 ? "dropdown-item" : "nav-link") + " dropdown-toggle"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${!hasHref && pathname === item.href ? "!text-[var(--current-color)]" : ""}`}
href="#" href="#"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
@ -112,7 +112,7 @@ export default function Nav({ color = "#fab758" }) {
return ( return (
<li key={item.id} className="nav-item"> <li key={item.id} className="nav-item">
<Link <Link
className={`${level > 0 ? "dropdown-item" : "nav-link"} !text-[.85rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${pathname === item.href ? "!text-[var(--current-color)]" : ""}`} className={`${level > 0 ? "dropdown-item" : "nav-link"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${pathname === item.href ? "!text-[var(--current-color)]" : ""}`}
href={item.href || "#"} href={item.href || "#"}
> >
{item.label} {item.label}

View File

@ -1,147 +1,182 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import ModalVideo from "@/components/common/ModalVideo"; import Image from "next/image";
import AnimatedText from "@/components/common/AnimatedText";
import Link from "next/link"; import Link from "next/link";
import axios from "axios"; import axios from "axios";
import { Autoplay, Navigation, Pagination } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
export default function Hero({ showNavButtons = false }) { export default function Hero() {
const [isOpen, setIsOpen] = useState(""); const [data, setData] = useState(null);
const [heroData, setHeroData] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
async function fetchHeroData() { async function fetchData() {
try { try {
setLoading(true); setLoading(true);
const res = await axios.get("/api/get-component-data", { const res = await axios.get("/api/get-component-data", {
params: { component_name: "Hero" } params: { component_name: "Hero" },
}); });
setHeroData(res.data.data); setData(res.data.data);
} catch (err) { } catch (err) {
setError("获取Hero数据失败"); setError("获取Features数据失败");
} finally { } finally {
setLoading(false); setLoading(false);
} }
} }
fetchHeroData(); fetchData();
}, []); }, []);
if (loading) return <div className="text-white text-center py-20">Loading...</div>; if (loading) return <div className="text-center py-20">加载中...</div>;
if (error) return <div className="text-red-500 text-center py-20">{error}</div>; if (error) return null;
if (!heroData) return null; if (!data) return null;
// items //
const items = heroData.items || []; const bgImage = data.bg_image || "/assets/img/photos/blurry.png";
// // //
const bgImages = [heroData.image, heroData.image_1, heroData.image_2].filter(Boolean); const title = data.title || "";
// const subtitle = data.subtitle || "";
const titles = [heroData.title, ...(items.map(i => i.item_title))].filter(Boolean); const description = data.description || "";
const subtitles = [heroData.subtitle, ...(items.map(i => i.item_subtitle))].filter(Boolean); const icon = data.icon || "/files/icon.svg";
const descs = [heroData.description, ...(items.map(i => i.item_description))].filter(Boolean); const htmlCode = data.html_code || "";
const buttonText = data.button_text || "MORE";
const buttonLink = data.button_link || "#";
const buttonText2 = data.button_2_text || "MORE";
const buttonLink2 = data.button_2_link || "#";
// items
// const items = data.items || [];
return ( return (
<> <div className="container pt-36 xl:pt-[12.5rem] lg:pt-[12.5rem] md:pt-[12.5rem] !text-center !relative">
<div className="swiper-container swiper-hero dots-over relative z-10"> <div
<Swiper className="absolute"
className="swiper h-full" style={{
modules={[Navigation, Pagination, Autoplay]} top: "-15%",
pagination={{ left: "50%",
clickable: true, transform: "translateX(-50%)",
el: ".spd7", }}
}} >
spaceBetween={0} <Image
speed={3000} className="!rounded-[0.8rem]"
navigation={showNavButtons ? { alt="image"
prevEl: ".snbp7", src={bgImage}
nextEl: ".snbn7", width={1400}
} : false} height={1080}
> />
{items.map((item, idx) => { </div>
let alignClass = ""; <div className="flex flex-wrap mx-[-15px] !relative">
switch ((item.content_align || "").toLowerCase()) { <div className="lg:w-8/12 xl:w-8/12 xxl:w-8/12 w-full flex-[0_0_auto] !px-[15px] max-w-full !mx-auto !relative">
case "center": <div
alignClass = "md:w-11/12 lg:w-8/12 xl:w-7/12 xxl:w-6/12 w-full !mx-auto text-center"; className="absolute shape grape w-5 hidden xl:block lg:block"
break; style={{ top: "-5%", left: "-15%" }}
case "right": >
alignClass = "md:w-10/12 lg:w-7/12 xl:w-6/12 w-full max-w-full text-center lg:text-right xl:text-right lg:!ml-[41.666667%] xl:!ml-[50%] xxl:!ml-[50%]"; <svg
break; xmlns="http://www.w3.org/2000/svg"
case "left": viewBox="0 0 219.5 219.5"
default: className="svg-inject icon-svg !w-full !h-full"
alignClass = "md:w-10/12 md:!ml-[8.33333333%] lg:w-7/12 lg:!ml-0 xl:w-6/12 xxl:w-5/12 w-full max-w-full text-center lg:text-left xl:text-left"; >
break; <path
} className="svg-fill"
return ( d="M219.5 219.5H0c0-58.19 23.14-114.06 64.29-155.21A219.561 219.561 0 01219.5 0v219.5z"
<SwiperSlide />
key={idx} </svg>
className="swiper-slide max-h-full bg-overlay bg-overlay-400 bg-[#21262c] opacity-100 bg-image !bg-cover !bg-[center_center] !h-[750px] before:content-[''] before:block before:absolute before:z-[1] before:w-full before:h-full before:left-0 before:top-0 before:bg-[rgba(30,34,40,.4)]" </div>
style={{ backgroundImage: `url(${item.item_image || bgImages[idx] || bgImages[0] || "/assets/img/photos/bg7.jpg"})` }} <div
className="absolute shape violet !w-[2.5rem] hidden xl:block lg:block"
style={{ bottom: "30%", left: "-20%" }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 457.71 446.45"
className="svg-inject icon-svg !w-full !h-full"
>
<path
className="svg-fill"
d="M205.62 442.54c-.51-.41-.99-.84-1.46-1.31-10.4-10.39-3.61-27.12 11.63-52.5 11.72-19.51 28.48-43.74 46.22-69.39 17.53-25.34 35.66-51.54 49.01-73.47 14.73-24.19 18.57-35.01 19.51-39.48-3.86-.28-13.15.3-33.78 6.52-19.71 5.94-43.09 14.92-65.7 23.6-63.8 24.49-85.35 31.41-96.51 19.5-3.21-3.43-7.62-11.04-1.12-23.54 2.49-4.79 6.64-10.51 12.68-17.5 10.4-12.03 25.04-26.34 40.55-41.49 14.02-13.7 28.53-27.87 39.53-40.11 9.93-11.04 14.15-17.43 15.94-20.82-3.43-.75-10.24-1.51-23.25-.92-14.99.68-33.44 2.89-51.28 5.02-46.92 5.61-74.09 8.33-86.81-.99-4.78-3.5-7.67-8.42-8.41-14.24-1.97-15.58 12.45-33.15 29.14-53.5 5.31-6.47 13.31-16.22 17.36-22.68-4.51-.38-12.43-.4-25.97 1.1-16.54 1.82-35.62 5.22-50.95 7.96-12.62 2.25-22.6 4.03-28.48 4.49C6.6 39.3.58 34.17.04 27.28c-.54-6.88 4.6-12.9 11.48-13.44 4.66-.37 14.58-2.13 26.06-4.18C54.56 6.63 75.7 2.86 94 1.07c26.41-2.6 40.16-.54 47.48 7.13 7.01 7.34 6.45 17.66-1.68 30.66-4.72 7.55-11.63 15.98-18.95 24.9-6.18 7.53-12.57 15.31-17.24 22.19-3.35 4.92-4.95 8.13-5.71 10.07 3 .54 9.09 1.08 20.87.43 13.21-.73 29.07-2.63 45.86-4.64 59.99-7.17 94.33-10.22 102.49 10.62 7.4 18.93-16.51 43.51-62.99 88.92-12.69 12.4-24.68 24.11-34.04 34.28-3.17 3.45-5.68 6.34-7.67 8.75 15.88-4.42 41.18-14.13 59.67-21.22 62.4-23.96 101.69-37.87 121.09-29.14 5.38 2.42 9.26 6.47 11.23 11.72 7.48 19.95-16.15 57.31-71.83 137.82-15.49 22.39-30.12 43.55-41.18 61.25-7.13 11.4-11.3 19.11-13.75 24.25 8.97-3.3 25.58-11 55.73-28.8 32.68-19.29 70.61-44.04 101.09-63.94 24.62-16.07 44.07-28.76 54.65-34.68 6.03-3.37 13.64-1.22 17.01 4.81 3.37 6.03 1.22 13.64-4.81 17.01-9.83 5.5-29.92 18.61-53.18 33.79-33.46 21.84-75.1 49.01-110.05 69.21-49.35 28.51-70.85 35.44-82.46 26.07z"
/>
</svg>
</div>
<div
className="absolute shape fuchsia w-6 hidden xl:block lg:block"
style={{
top: "0%",
right: "-25%",
transform: "rotate(70deg)",
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 411.42 329.24"
className="svg-inject icon-svg !w-full !h-full"
>
<g data-name="Layer 2">
<path
className="svg-fill"
d="M164.34 21.92L8.72 251.15c-22.54 33.2 1.24 78.09 41.37 78.09h311.24c40.13 0 63.91-44.89 41.37-78.09L247.08 21.92a50 50 0 00-82.74 0z"
data-name="Layer 1"
/>
</g>
</svg>
</div>
<div
className="absolute shape yellow w-6 hidden xl:block lg:block"
style={{ bottom: "25%", right: "-17%" }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 500 500"
className="svg-inject icon-svg !w-full !h-full"
>
<g data-name="Layer 2">
<path
className="svg-fill"
d="M250 0C111.93 0 0 111.93 0 250s111.93 250 250 250 250-111.93 250-250S388.07 0 250 0zm0 425a175 175 0 11175-175 175 175 0 01-175 175z"
data-name="Layer 1"
/>
</g>
</svg>
</div>
<div>
<h1 className="xl:!text-[3.2rem] !text-[calc(1.445rem_+_2.34vw)] font-semibold !leading-[1.15] !mb-5 md:mx-10 lg:mx-0 xl:mx-0">
{title}
</h1>
<h2 className="xl:!text-[2.2rem] !text-[calc(1.445rem_+_2.34vw)] font-semibold !leading-[1.15] !mb-5 md:mx-10 lg:mx-0 xl:mx-0">
<span className="rotator-fade !text-[#333333]">
<AnimatedText
strings={[
data.p1,
data.p2,
data.p3
].filter(Boolean)}
/>
</span>
</h2>
<p className="lead !text-[1.2rem] !leading-[1.6] !mb-8">
{subtitle}
</p>
</div>
<div
className="flex justify-center"
>
<span>
<Link
href={buttonLink}
className="btn btn-lg btn-grape !text-white !bg-[#1fc76f] hover:text-white hover:bg-[#1fc76f] hover:!border-[#1fc76f] active:text-white active:bg-[#1fc76f] active:border-[#1fc76f] disabled:text-white disabled:bg-[#1fc76f] disabled:border-[#1fc76f] !rounded-[0.8rem] mx-1"
> >
<div className="container !h-full"> {buttonText}
<div className="flex flex-wrap !h-full items-center"> </Link>
<div className={alignClass + " relative z-10"}> </span>
<h2 className="xl:!text-[2.8rem] !leading-[1.2] font-bold !text-[calc(1.405rem_+_1.86vw)] !mb-4 !text-white animate__animated animate__slideInDown animate__delay-1s"> <span>
{item.item_title || heroData.title} <Link
</h2> href={buttonLink2}
<p className="lead text-[1.15rem] leading-normal !mb-7 !text-white animate__animated animate__slideInRight animate__delay-2s"> className="btn btn-lg btn-fuchsia !text-white !bg-[#1A1A1A] hover:text-white hover:bg-[#1A1A1A] hover:!border-[#1A1A1A] active:text-white active:bg-[#1A1A1A] active:border-[#1A1A1A] disabled:text-white disabled:bg-[#1A1A1A] disabled:border-[#1A1A1A] !rounded-[0.8rem] mx-1"
{item.item_subtitle || heroData.subtitle} >
</p> {buttonText2}
<div className="animate__animated animate__slideInUp animate__delay-3s"> </Link>
{item.item_video_src ? ( </span>
<a </div>
onClick={() => setIsOpen(item.item_video_src)}
className="btn btn-circle btn-white btn-play ripple !mx-auto !mb-5 !relative z-[2] xl:!text-[2.3rem] !w-[3.5rem] !h-[3.5rem] !text-[calc(1.355rem_+_1.26vw)] hover:translate-y-0 inline-flex items-center justify-center leading-none p-0 !rounded-[100%] before:content-[''] before:block before:absolute before:opacity-80 before:animate-[ripple-1_2s_infinite_ease-in-out] before:z-[-1] before:rounded-[50%] before:inset-0 before:bg-[#ffffff] after:content-[''] after:block after:absolute after:opacity-60 after:animate-[ripple-2_2s_infinite_ease-in-out] after:z-[-1] after:rounded-[50%] after:inset-0 after:bg-[#ffffff]"
style={{ cursor: "pointer" }}
>
<i className="icn-caret-right !ml-[0.15rem] !relative z-[2] before:content-['\\e900'] !text-[calc(1.355rem_+_1.26vw)]" />
</a>
) : (
item.item_button_text && item.item_button_link && (
item.item_button_link.startsWith("http") ? (
<a
href={item.item_button_link}
className="btn btn-outline-white !text-white bg-[#ffffff] !border-white !border-[2px] hover:!text-[#343f52] hover:bg-[#ffffff] hover:border-white focus:shadow-[rgba(255,255,255,1)] active:!text-[#343f52] active:bg-[#ffffff] active:border-white disabled:text-white disabled:bg-transparent disabled:border-white !rounded-[50rem]"
target="_blank"
rel="noopener noreferrer"
>
{item.item_button_text}
</a>
) : (
<Link
href={item.item_button_link}
className="btn btn-outline-white !text-white bg-[#ffffff] !border-white !border-[2px] hover:!text-[#343f52] hover:bg-[#ffffff] hover:border-white focus:shadow-[rgba(255,255,255,1)] active:!text-[#343f52] active:bg-[#ffffff] active:border-white disabled:text-white disabled:bg-transparent disabled:border-white !rounded-[50rem]"
>
{item.item_button_text}
</Link>
)
)
)}
</div>
</div>
</div>
</div>
</SwiperSlide>
);
})}
</Swiper>
<div className="swiper-controls">
{showNavButtons && (
<div className="swiper-navigation">
<div className="swiper-button swiper-button-prev snbp7"></div>
<div className="swiper-button swiper-button-next snbn7"></div>
</div>
)}
<div className="swiper-pagination spd7"></div>
</div> </div>
</div> </div>
</div>
<ModalVideo
isOpen={!!isOpen}
setIsOpen={() => setIsOpen("")}
src={isOpen}
/>
</>
); );
} }