feat(web): landing 4-item nav, section relabels, eco-materials section
This commit is contained in:
parent
ef35204bc6
commit
208c6b93ba
|
|
@ -0,0 +1,250 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { history } from '@umijs/max'
|
||||
import { LOC_STOR_KEY } from '@/common/constants'
|
||||
import PhoneAuthModal from '@/components/phone-auth-modal'
|
||||
import api from '@/services/api'
|
||||
import type { EcoMaterial } from '@/services/api/material'
|
||||
import './landing.css'
|
||||
|
||||
const LeafIcon = () => (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"><path d="M4 20c10 2 16-4 16-14 0 0-8-2-12 2-3 3-3 7-1 9 3-4 6-6 9-7" /></svg>
|
||||
)
|
||||
const ArrowIcon = ({ small }: { small?: boolean }) => (
|
||||
<svg viewBox="0 0 24 24" width={small ? 15 : undefined} height={small ? 15 : undefined} fill="none" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
|
||||
)
|
||||
const CheckIcon = () => (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5" /></svg>
|
||||
)
|
||||
const ChevronIcon = ({ dir }: { dir: 'left' | 'right' }) => (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.4} strokeLinecap="round" strokeLinejoin="round"><path d={dir === 'left' ? 'M15 5l-7 7 7 7' : 'M9 5l7 7-7 7'} /></svg>
|
||||
)
|
||||
|
||||
const stats = [
|
||||
{ n: '28+', t: '在管装修项目' },
|
||||
{ n: '2,400+', t: '累计预测次数' },
|
||||
{ n: '6 项', t: '污染物 · 甲醛/苯/TVOC/氨/氡/VOC' },
|
||||
{ n: '2 部', t: '国标依据 · 18883 / 50325' }
|
||||
]
|
||||
const news = [
|
||||
{ tag: '政策解读', date: '专栏 · 2026.05', title: '两大国标怎么读?GB/T 18883 与 GB 50325 的差别', desc: '一个是"住进去后"的室内空气质量标准,一个是"交工验收时"的工程控制标准——限值与采样条件并不相同,看懂它们才能判断房子到底达不达标。' },
|
||||
{ tag: '科普', date: '专栏 · 2026.04', title: '新装住宅的甲醛,为什么能持续释放 3–15 年?', desc: '人造板里的脲醛树脂胶会缓慢分解释放甲醛,释放周期长、受温湿度影响大。短期通风只能降一时浓度,真正的关键在源头材料的选择。' },
|
||||
{ tag: '指南', date: '专栏 · 2026.03', title: '夏天为什么更容易超标?温度与释放速率', desc: '温度每升高若干度,材料的甲醛释放速率会明显增大。这也是"冬天测达标、夏天又超标"的原因。预测时把环境温湿度纳入计算,结果才靠谱。' },
|
||||
{ tag: '方法', date: '专栏 · 2026.02', title: '先预测,再决定要不要做 CMA 检测', desc: '上门检测有成本。用本系统先做一次免费预测、定位高风险房间与主要污染材料,再有针对性地安排第三方 CMA 检测,省钱也更有的放矢。' }
|
||||
]
|
||||
const cases = [
|
||||
{ type: '住宅 · I类民用建筑', name: '锦绣华庭 · 主卧', meta: '主源:多层实木复合地板 · 人造板衣柜', w1: 82, v1: '0.18', w2: 36, v2: '0.08', chip: '甲醛达标' },
|
||||
{ type: '住宅 · I类民用建筑', name: '翠湖天地 · 儿童房', meta: '主源:人造板衣柜 · 壁纸基膜', w1: 95, v1: '0.21', w2: 32, v2: '0.07', chip: '甲醛达标' },
|
||||
{ type: '酒店客房 · II类民用建筑', name: '云栖精选酒店 · 标准间', meta: '主源:木器漆饰面 · 软包', w1: 90, v1: '0.72', w2: 58, v2: '0.46', chip: 'TVOC达标' }
|
||||
]
|
||||
const steps = [
|
||||
{ h: '录入房间与材料', p: '选择房间、填写面积层高与通风换气率,勾选所用装修材料及用量。' },
|
||||
{ h: '公式计算浓度', p: '按稳态质量平衡 C = Σ(EFᵢ·Aᵢ)/(n·V) 计算 6 项污染物浓度,对照国标判定达标。' },
|
||||
{ h: '溯源与整改建议', p: '若超标,公式溯源出贡献最大的材料并排序,给出通风或换材的可行整改方案。' }
|
||||
]
|
||||
|
||||
const hasToken = () => !!localStorage.getItem(LOC_STOR_KEY.AUTH_TOKEN)
|
||||
const goPro = () => history.push(hasToken() ? '/home' : '/login')
|
||||
const scrollTop = () => window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
const scrollToId = (id: string) => document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' })
|
||||
|
||||
export default function Landing() {
|
||||
const [cur, setCur] = useState(0)
|
||||
const timer = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const go = (n: number) => setCur((((n % news.length) + news.length) % news.length))
|
||||
const stop = () => { if (timer.current) { clearInterval(timer.current); timer.current = null } }
|
||||
const start = () => { stop(); timer.current = setInterval(() => setCur(c => (c + 1) % news.length), 5500) }
|
||||
const next = () => { setCur(c => (c + 1) % news.length); start() }
|
||||
const prev = () => { setCur(c => (c - 1 + news.length) % news.length); start() }
|
||||
useEffect(() => { start(); return stop }, [])
|
||||
|
||||
const [authOpen, setAuthOpen] = useState(false)
|
||||
const [ecoList, setEcoList] = useState<EcoMaterial[]>([])
|
||||
useEffect(() => {
|
||||
api.material.getEcoMaterials().then(setEcoList).catch(() => setEcoList([]))
|
||||
}, [])
|
||||
const openPredict = () => {
|
||||
if (hasToken()) history.push('/source')
|
||||
else setAuthOpen(true)
|
||||
}
|
||||
const onAuthed = () => {
|
||||
setAuthOpen(false)
|
||||
history.push('/source')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="lp">
|
||||
<header className="nav">
|
||||
<div className="nav-in">
|
||||
<a className="brand" onClick={scrollTop}>
|
||||
<span className="brand-logo"><LeafIcon /></span>
|
||||
<span><span className="brand-tt">装修室内空气质量预测系统</span><br /><span className="brand-sub">INDOOR AIR · 装修污染</span></span>
|
||||
</a>
|
||||
<nav className="nav-links">
|
||||
<a onClick={() => scrollToId('news')}>健康装修科普</a>
|
||||
<a onClick={() => scrollToId('cases')}>预测案例</a>
|
||||
<a onClick={() => scrollToId('eco')}>环保建材</a>
|
||||
<a onClick={() => history.push('/forum')}>健康装修大家谈</a>
|
||||
</nav>
|
||||
<span className="nav-sp" />
|
||||
<a className="btn btn-primary" onClick={openPredict}>免费试算<ArrowIcon /></a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="hero">
|
||||
<div className="wrap hero-grid">
|
||||
<div>
|
||||
<span className="eyebrow"><span className="pip" />对照 GB/T 18883-2022 与 GB 50325-2020 双国标</span>
|
||||
<h1>装修住得安心,<br />从一次<em>污染预测</em>开始</h1>
|
||||
<p className="hero-lead">输入房间、环境与所用材料,系统用稳态质量平衡公式预测甲醛、苯、TVOC 等 6 项污染物浓度,判定是否超标,并溯源到具体污染材料,给出整改建议。</p>
|
||||
<div className="hero-cta">
|
||||
<a className="btn btn-primary btn-lg" onClick={openPredict}>免费预测甲醛 · TVOC<ArrowIcon /></a>
|
||||
<a className="btn btn-lg" onClick={() => scrollToId('cases')}>查看治理案例</a>
|
||||
</div>
|
||||
<div className="hero-tags">
|
||||
<span className="hero-tag"><CheckIcon />无需上门,先估后测</span>
|
||||
<span className="hero-tag"><CheckIcon />公式可溯源</span>
|
||||
<span className="hero-tag"><CheckIcon />材料数据库支撑</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-visual">
|
||||
<div className="img-ph hero-img">室内 / 装修实景照片</div>
|
||||
<div className="hero-badge"><b>6 项</b><span>污染物预测</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="stats">
|
||||
<div className="stats-in">
|
||||
{stats.map((s, i) => <div className="stat" key={i}><b>{s.n}</b><span>{s.t}</span></div>)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sec" id="news">
|
||||
<div className="wrap">
|
||||
<div className="sec-head">
|
||||
<div><div className="sec-tag">健康装修科普</div><h2 className="sec-h">读懂装修污染,先把知识装进脑子</h2></div>
|
||||
<a className="btn" onClick={openPredict}>全部文章</a>
|
||||
</div>
|
||||
<div className="carousel">
|
||||
<div className="cviewport">
|
||||
<div className="ctrack" style={{ transform: `translateX(${-cur * 100}%)` }}>
|
||||
{news.map((n, i) => (
|
||||
<article className="cslide" key={i}>
|
||||
<div className="cslide-img"><span className="cslide-tag">{n.tag}</span><div className="img-ph">资讯配图</div></div>
|
||||
<div className="cslide-body">
|
||||
<div className="cslide-date">{n.date}</div>
|
||||
<h3>{n.title}</h3>
|
||||
<p>{n.desc}</p>
|
||||
<span className="cslide-more">阅读全文<ArrowIcon /></span>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<button className="cnav prev" onClick={prev}><ChevronIcon dir="left" /></button>
|
||||
<button className="cnav next" onClick={next}><ChevronIcon dir="right" /></button>
|
||||
</div>
|
||||
<div className="cdots">
|
||||
{news.map((_, i) => <button key={i} className={'cdot' + (i === cur ? ' on' : '')} onClick={() => go(i)} />)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sec" id="cases" style={{ background: 'var(--bg2)' }}>
|
||||
<div className="wrap">
|
||||
<div className="sec-head">
|
||||
<div><div className="sec-tag">预测案例</div><h2 className="sec-h">预测 → 溯源 → 整改,看得见的下降</h2><p className="sec-sub">真实流程演示:从预测超标,到锁定主要污染材料,再到整改复测达标。</p></div>
|
||||
</div>
|
||||
<div className="cases-grid">
|
||||
{cases.map((c, i) => (
|
||||
<article className="case" key={i}>
|
||||
<div className="img-ph">案例实景图</div>
|
||||
<div className="case-b">
|
||||
<div className="case-type">{c.type}</div>
|
||||
<h4>{c.name}</h4>
|
||||
<div className="case-meta">{c.meta}</div>
|
||||
<div className="ba">
|
||||
<div className="ba-row"><span className="k">治理前</span><div className="ba-track"><div className="ba-fill" style={{ width: c.w1 + '%', background: 'var(--bad)' }} /></div><span className="v" style={{ color: 'var(--bad)' }}>{c.v1}</span></div>
|
||||
<div className="ba-row"><span className="k">治理后</span><div className="ba-track"><div className="ba-fill" style={{ width: c.w2 + '%', background: 'var(--good)' }} /></div><span className="v" style={{ color: 'var(--good)' }}>{c.v2}</span></div>
|
||||
</div>
|
||||
<div className="case-foot"><span className="chip chip-good">{c.chip}</span><span className="lk">查看溯源<ArrowIcon small /></span></div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{ecoList.length > 0 && (
|
||||
<section className="sec" id="eco">
|
||||
<div className="wrap">
|
||||
<div className="sec-head">
|
||||
<div>
|
||||
<div className="sec-tag">环保建材</div>
|
||||
<h2 className="sec-h">优选低释放材料,从源头减少污染</h2>
|
||||
<p className="sec-sub">以下为材料库中环保等级 E0/E1 的低释放建材,选材阶段即可降低甲醛与 TVOC 风险。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cases-grid">
|
||||
{ecoList.map(m => (
|
||||
<article className="case" key={m.material_id}>
|
||||
<div className="case-b">
|
||||
<div className="case-type">{m.category} · {m.brand}</div>
|
||||
<h4>{m.name || m.brand || '环保建材'}</h4>
|
||||
<div className="case-meta">{m.factory || '—'}</div>
|
||||
<div className="case-foot">
|
||||
<span className="chip chip-good">环保 {m.eco_level}</span>
|
||||
{typeof m.methanal === 'number' && <span className="lk">甲醛 {m.methanal}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section className="sec" id="how">
|
||||
<div className="wrap">
|
||||
<div className="sec-head"><div><div className="sec-tag">如何使用</div><h2 className="sec-h">三步,得到一份可溯源的预测报告</h2></div></div>
|
||||
<div className="steps">
|
||||
{steps.map((s, i) => (
|
||||
<div className="step" key={i}>
|
||||
<div className="step-n">{i + 1}</div><h4>{s.h}</h4><p>{s.p}</p>
|
||||
{i < steps.length - 1 && <div className="step-ar"><ArrowIcon /></div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sec" style={{ paddingTop: 0 }}>
|
||||
<div className="wrap">
|
||||
<div className="ctaband">
|
||||
<div style={{ position: 'relative', zIndex: 2 }}>
|
||||
<h2>现在就免费预测<br />你家会不会甲醛超标</h2>
|
||||
<p>无需上门,输入材料即可估算。先估后测,把检测的钱花在刀刃上。</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 14, position: 'relative', zIndex: 2 }}>
|
||||
<a className="btn btn-primary btn-lg" onClick={openPredict}>免费开始预测</a>
|
||||
<a className="btn btn-lg" onClick={goPro}>查看专业看板</a>
|
||||
</div>
|
||||
<div className="deco" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer className="footer">
|
||||
<div className="footer-in">
|
||||
<div className="brand">
|
||||
<span className="brand-logo"><LeafIcon /></span>
|
||||
<span className="brand-tt">装修室内空气质量预测系统</span>
|
||||
</div>
|
||||
<div>依据 GB/T 18883-2022 · GB 50325-2020 · 预测结果仅供参考,以 CMA 检测为准</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<PhoneAuthModal open={authOpen} onSuccess={onAuthed} onCancel={() => setAuthOpen(false)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue