first commit
This commit is contained in:
427
src/App.jsx
Normal file
427
src/App.jsx
Normal file
@@ -0,0 +1,427 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import './index.css'
|
||||
|
||||
// 导航栏组件
|
||||
function Navbar() {
|
||||
const [isScrolled, setIsScrolled] = useState(false)
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [activeSection, setActiveSection] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 50)
|
||||
|
||||
// 检测当前活跃的 section
|
||||
const sections = ['coupon', 'faq', 'contact']
|
||||
for (const sectionId of sections) {
|
||||
const element = document.getElementById(sectionId)
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect()
|
||||
if (rect.top <= 100 && rect.bottom >= 100) {
|
||||
setActiveSection(sectionId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [])
|
||||
|
||||
const scrollToSection = (e, sectionId) => {
|
||||
e.preventDefault()
|
||||
if (sectionId === 'page-top') {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
} else {
|
||||
const element = document.getElementById(sectionId)
|
||||
if (element) {
|
||||
const offset = element.offsetTop - 50
|
||||
window.scrollTo({ top: offset, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
setIsMenuOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className={`navbar-default ${isScrolled ? 'affix' : ''}`}>
|
||||
<div className="container">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<a
|
||||
className="navbar-brand"
|
||||
href="#page-top"
|
||||
onClick={(e) => scrollToSection(e, 'page-top')}
|
||||
>
|
||||
<img className="iconlogo" src="/img/iconlogo.png" width="50" alt="Logo" />
|
||||
ZU CHRISTMAS
|
||||
</a>
|
||||
|
||||
<button
|
||||
className="navbar-toggle"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
>
|
||||
Menu <i className="fa fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<ul className={`nav-links ${isMenuOpen ? 'open' : ''}`} style={{
|
||||
display: 'flex',
|
||||
listStyle: 'none',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
gap: '20px',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
<li>
|
||||
<a
|
||||
className={`nav-link ${activeSection === 'coupon' ? 'active' : ''}`}
|
||||
href="#coupon"
|
||||
onClick={(e) => scrollToSection(e, 'coupon')}
|
||||
>
|
||||
Special Offer
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={`nav-link ${activeSection === 'faq' ? 'active' : ''}`}
|
||||
href="#faq"
|
||||
onClick={(e) => scrollToSection(e, 'faq')}
|
||||
>
|
||||
F.A.Q.
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={`nav-link ${activeSection === 'contact' ? 'active' : ''}`}
|
||||
href="#contact"
|
||||
onClick={(e) => scrollToSection(e, 'contact')}
|
||||
>
|
||||
Contact
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="nav-link menuhighlight"
|
||||
href="#"
|
||||
>
|
||||
<i className="fa fa-cloud-download"></i> Download
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
// 粒子动画背景
|
||||
function Illustration() {
|
||||
return (
|
||||
<div className="illustration">
|
||||
<div className="i-large"></div>
|
||||
<div className="i-medium"></div>
|
||||
<div className="i-small"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Header 区域
|
||||
function Header() {
|
||||
const audioRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.volume = 0.4
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<header id="page-top">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<div className="device-container">
|
||||
<img
|
||||
src="/img/homeimg.png"
|
||||
alt="Home"
|
||||
style={{ maxWidth: '100%', height: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<div className="header-content">
|
||||
<div className="header-content-inner">
|
||||
<h1>Merry Holidays</h1>
|
||||
<h4>Make your visitors smile with this special landing page!</h4>
|
||||
<br />
|
||||
<a href="#" className="btnimg">
|
||||
<img src="/img/button-available.png" height="50" alt="App Store" />
|
||||
</a>
|
||||
{' '}
|
||||
<a href="#" className="btnimg">
|
||||
<img src="/img/button-googleplay.png" height="50" alt="Google Play" />
|
||||
</a>
|
||||
<br /><br />
|
||||
<div className="social">
|
||||
<a href="#"><i className="fa fa-facebook"></i></a>
|
||||
<a href="#"><i className="fa fa-twitter"></i></a>
|
||||
<a href="#"><i className="fa fa-google-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="audioembed">
|
||||
<audio
|
||||
ref={audioRef}
|
||||
controls
|
||||
autoPlay
|
||||
src="/audio/audio.mp3"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
// 优惠券区域
|
||||
function CouponSection() {
|
||||
return (
|
||||
<section id="coupon" className="couponsection bg-primary text-center">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-md-10 col-md-offset-1">
|
||||
<h2 className="title section-heading">December Only</h2>
|
||||
<p>Get your discount, available in stores this month only</p>
|
||||
<div className="coupon">
|
||||
<img src="/img/gift.png" alt="Gift" />
|
||||
<h3>50% Off All Products!</h3>
|
||||
<h4>Coupon Code: <span style={{ borderBottom: '1px dashed' }}>HOLIDAY50</span></h4>
|
||||
<br />
|
||||
<p>
|
||||
<i>
|
||||
Use this coupon code at checkout to receive<br />
|
||||
<u>half off</u> your order. Hurry up, limited time offer!
|
||||
</i>
|
||||
</p>
|
||||
<p>
|
||||
<a href="#" className="btn-buynow">
|
||||
<i className="fa fa-shopping-cart"></i> Shop Now
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
// FAQ 区域
|
||||
function FAQSection() {
|
||||
const faqs = [
|
||||
{
|
||||
question: "How soon do I get my item?",
|
||||
answer: "If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text."
|
||||
},
|
||||
{
|
||||
question: "Is there a free shipping?",
|
||||
answer: "If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text."
|
||||
},
|
||||
{
|
||||
question: "Can I get several discounts?",
|
||||
answer: "If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text."
|
||||
},
|
||||
{
|
||||
question: "Do you deliver it as a gift?",
|
||||
answer: "If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text."
|
||||
},
|
||||
{
|
||||
question: "What payment methods?",
|
||||
answer: "If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text."
|
||||
},
|
||||
{
|
||||
question: "I have a different question?",
|
||||
answer: "If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text."
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<section id="faq">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-lg-12 text-center">
|
||||
<div className="section-heading">
|
||||
<h2 className="title">F.A.Q.</h2>
|
||||
<p className="text-muted">Contact us if you have a different question</p>
|
||||
<img src="/img/sep.png" width="120" alt="Separator" className="sep-img" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-10 col-md-offset-1">
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
{faqs.slice(0, 3).map((faq, index) => (
|
||||
<div key={index}>
|
||||
<h3>{faq.question}</h3>
|
||||
<p>{faq.answer}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
{faqs.slice(3, 6).map((faq, index) => (
|
||||
<div key={index}>
|
||||
<h3>{faq.question}</h3>
|
||||
<p>{faq.answer}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
// 联系区域
|
||||
function ContactSection() {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
comment: ''
|
||||
})
|
||||
const [errors, setErrors] = useState({})
|
||||
const [showSuccess, setShowSuccess] = useState(false)
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}))
|
||||
// 清除错误
|
||||
if (errors[name]) {
|
||||
setErrors(prev => ({
|
||||
...prev,
|
||||
[name]: false
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
// 验证
|
||||
const newErrors = {}
|
||||
if (!formData.name.trim()) newErrors.name = true
|
||||
if (!formData.email.trim() || !/\S+@\S+\.\S+/.test(formData.email)) newErrors.email = true
|
||||
if (!formData.comment.trim()) newErrors.comment = true
|
||||
|
||||
if (Object.keys(newErrors).length > 0) {
|
||||
setErrors(newErrors)
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟提交成功
|
||||
setShowSuccess(true)
|
||||
setFormData({ name: '', email: '', comment: '' })
|
||||
|
||||
setTimeout(() => {
|
||||
setShowSuccess(false)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
const closeAlert = () => {
|
||||
setShowSuccess(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="contact">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-lg-12 text-center">
|
||||
<div className="section-heading">
|
||||
<h2 className="title" style={{ marginBottom: '60px', color: '#fff' }}>Contact Us</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-8 col-lg-offset-2">
|
||||
<div className={`done ${showSuccess ? 'show' : ''}`}>
|
||||
<div className="alert-success">
|
||||
<button type="button" className="close" onClick={closeAlert}>×</button>
|
||||
Your message has been sent. Thank you!
|
||||
</div>
|
||||
</div>
|
||||
<form id="contactform" onSubmit={handleSubmit}>
|
||||
<div className="form">
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="Name *"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
className={errors.name ? 'error' : ''}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
placeholder="E-mail *"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className={errors.email ? 'error' : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
name="comment"
|
||||
rows="7"
|
||||
placeholder="Message *"
|
||||
value={formData.comment}
|
||||
onChange={handleChange}
|
||||
className={errors.comment ? 'error' : ''}
|
||||
/>
|
||||
<input type="submit" className="clearfix btn" value="Send" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overlay"></div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
// 页脚
|
||||
function Footer() {
|
||||
return (
|
||||
<footer>
|
||||
<div className="container">
|
||||
<p>© 2025 Your Company Name. All Rights Reserved.</p>
|
||||
<ul>
|
||||
<li><a href="#">Privacy</a></li>
|
||||
<li><a href="#">Terms</a></li>
|
||||
<li><a href="#">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
// 主应用
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<Illustration />
|
||||
<Navbar />
|
||||
<Header />
|
||||
<CouponSection />
|
||||
<FAQSection />
|
||||
<ContactSection />
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
1063
src/index.css
Normal file
1063
src/index.css
Normal file
File diff suppressed because it is too large
Load Diff
10
src/main.jsx
Normal file
10
src/main.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
Reference in New Issue
Block a user