修改主页和机型信息展示页面

This commit is contained in:
huanglinhuan
2025-12-09 14:23:22 +08:00
parent 863fb26040
commit d50b08e33e
11 changed files with 237 additions and 803 deletions

View File

@@ -12,6 +12,12 @@ const ProductDetail = () => {
const [sections, setSections] = useState([]);
const [expanded, setExpanded] = useState({});
const [specsLoading, setSpecsLoading] = useState(false);
const [imgOpen, setImgOpen] = useState(true);
const [highlightsOpen, setHighlightsOpen] = useState(false);
const [compareOpen, setCompareOpen] = useState(true);
const [promoOpen, setPromoOpen] = useState(false);
const [faqOpen, setFaqOpen] = useState(false);
const [reviewOpen, setReviewOpen] = useState(false);
useEffect(() => {
const srcs = product
@@ -29,10 +35,10 @@ const ProductDetail = () => {
const loadImages = async () => {
try {
const qs = new URLSearchParams({ series, sub }).toString();
const res = await fetch(`http://localhost:3001/api/docs/images?${qs}`);
const res = await fetch(`/api/docs/images?${qs}`);
const data = await res.json();
if (data.success && Array.isArray(data.data) && data.data.length) {
setImages(data.data.map((d) => `http://localhost:3001${d.url}`));
setImages(data.data.map((d) => `${d.url}`));
}
} catch {}
};
@@ -44,17 +50,17 @@ const ProductDetail = () => {
try {
setSpecsLoading(true);
const qs = new URLSearchParams({ series, sub }).toString();
const res = await fetch(`http://localhost:3001/api/docs/specs?${qs}`);
const res = await fetch(`/api/docs/specs?${qs}`);
const data = await res.json();
if (data.success && Array.isArray(data.data)) {
if (data.data.length && typeof data.data[0]?.title === 'string' && Array.isArray(data.data[0]?.rows)) {
setSections(data.data);
setSpecs([]);
setExpanded({});
setExpanded(Object.fromEntries(data.data.map((_, i) => [i, true])));
} else {
setSections([{ title: '参数', rows: data.data }]);
setSpecs(data.data);
setExpanded({});
setExpanded({ 0: true });
}
}
} catch {}
@@ -89,90 +95,129 @@ const ProductDetail = () => {
return (
<div className="product-detail-page">
<div className="container">
<div className="carousel-full">
<div className="section-tag tag-images">机型图片</div>
{images.length > 1 && <button className="arrow left" onClick={prevSlide}></button>}
<div className="slides">
{images.map((src, idx) => (
<div key={idx} className={`slide ${idx === active ? 'active' : ''}`}>
<img className="slide-img" src={src} alt={`${product.name}-${idx+1}`} loading="lazy" />
</div>
))}
</div>
{images.length > 1 && <button className="arrow right" onClick={nextSlide}></button>}
{images.length > 1 && (
<div className="indicators">
{images.map((_, idx) => (
<span
key={idx}
className={`dot ${idx === active ? 'active' : ''}`}
onClick={() => setActive(idx)}
/>
))}
</div>
)}
</div>
<div className="specs-section">
<div className="section-narrow">
<div className="section-tag tag-specs">详细参数</div>
{specsLoading ? (
<div className="specs-body"><table className="specs-table"><tbody><tr><td colSpan="2" className="specs-val">加载中...</td></tr></tbody></table></div>
) : sections.length ? (
sections.map((sec, i) => {
const isOpen = !!expanded[i];
const toggle = () => setExpanded((prev) => ({ ...prev, [i]: !prev[i] }));
return (
<div key={sec.title + i} className="specs-group">
<button className="specs-group-header" onClick={toggle}>
{sec.title} {isOpen ? '▲' : '▼'}
</button>
{isOpen && (
<div className="specs-body">
<table className="specs-table">
<tbody>
{sec.rows.length ? (
sec.rows.map((row) => {
const k = String(row.key || '');
const v = String(row.value || '');
const kb = /^\*\*.*\*\*$/.test(k) || k.startsWith('!');
const vb = /^\*\*.*\*\*$/.test(v) || v.startsWith('!');
const bold = kb || vb;
const cleanKey = k.replace(/^!/, '').replace(/^\*\*/, '').replace(/\*\*$/, '');
const cleanVal = v.replace(/^!/, '').replace(/^\*\*/, '').replace(/\*\*$/, '');
return (
<tr key={row.key} className={bold ? 'specs-row-bold' : ''}>
<td className={`specs-key ${bold ? 'specs-bold' : ''}`}>{cleanKey}</td>
<td className={`specs-val ${bold ? 'specs-bold' : ''}`}>{cleanVal}</td>
</tr>
);
})
) : (
<tr><td className="specs-val" colSpan="2">暂无参数</td></tr>
)}
</tbody>
</table>
</div>
)}
<div className="pd-title">{product.name}{product.desc ? ` | ${product.desc}` : ''}</div>
<div className="section-narrow">
<div className="pd-list">
<button className="pd-list-item" onClick={() => setImgOpen(!imgOpen)}>
<span>产品图片</span>
<span>{imgOpen ? 'v' : ''}</span>
</button>
{imgOpen && (
<div className="pd-list-body">
{images.length > 1 ? (
<div className="size-carousel">
<div className="size-slides">
{images.map((src, idx) => (
<div key={idx} className={`size-slide ${idx === active ? 'active' : ''}`}>
<img className="size-slide-img" src={src} alt={`${product.name}-${idx+1}`} loading="lazy" />
</div>
))}
</div>
<button className="size-arrow left" onClick={prevSlide}></button>
<button className="size-arrow right" onClick={nextSlide}></button>
<div className="size-indicators">
{images.map((_, idx) => (
<span
key={idx}
className={`size-dot ${idx === active ? 'active' : ''}`}
onClick={() => setActive(idx)}
/>
))}
</div>
</div>
);
})
) : (
<div className="specs-body"><table className="specs-table"><tbody><tr><td colSpan="2" className="specs-val">暂无参数</td></tr></tbody></table></div>
) : (
<div className="image-row">
{(images.length ? images : (product?.image ? [product.image] : [])).map((src, idx) => (
<img key={idx} src={src} alt={`${product.name}-${idx+1}`} loading="lazy" />
))}
</div>
)}
</div>
)}
<button className="pd-list-item" onClick={() => setHighlightsOpen(!highlightsOpen)}>
<span>通信功能</span>
<span>{highlightsOpen ? 'v' : ''}</span>
</button>
{highlightsOpen && <div className="pd-list-body"></div>}
<button className="pd-list-item" onClick={() => setCompareOpen(!compareOpen)}>
<span>详细参数</span>
<span>{compareOpen ? 'v' : ''}</span>
</button>
{compareOpen && (
<div className="pd-list-body">
<div className="specs-sublist">
{specsLoading ? (
<div className="specs-body"><table className="specs-table"><tbody><tr><td colSpan="2" className="specs-val">加载中...</td></tr></tbody></table></div>
) : sections.length ? (
sections.map((sec, i) => {
const isOpen = !!expanded[i];
const toggle = () => setExpanded((prev) => ({ ...prev, [i]: !prev[i] }));
return (
<div key={sec.title + i} className="specs-group">
<button className="specs-group-header" onClick={toggle}>
{sec.title} {isOpen ? 'v' : ''}
</button>
{isOpen && (
<div className="specs-body">
<table className="specs-table">
<tbody>
{sec.rows.length ? (
sec.rows.map((row) => {
const k = String(row.key || '');
const v = String(row.value || '');
const kb = /^\*\*.*\*\*$/.test(k) || k.startsWith('!');
const vb = /^\*\*.*\*\*$/.test(v) || v.startsWith('!');
const bold = kb || vb;
const cleanKey = k.replace(/^!/, '').replace(/^\*\*/, '').replace(/\*\*$/, '');
const cleanVal = v.replace(/^!/, '').replace(/^\*\*/, '').replace(/\*\*$/, '');
return (
<tr key={row.key} className={bold ? 'specs-row-bold' : ''}>
<td className={`specs-key ${bold ? 'specs-bold' : ''}`}>{cleanKey}</td>
<td className={`specs-val ${bold ? 'specs-bold' : ''}`}>{cleanVal}</td>
</tr>
);
})
) : (
<tr><td className="specs-val" colSpan="2">暂无参数</td></tr>
)}
</tbody>
</table>
</div>
)}
</div>
);
})
) : (
<div className="specs-body"><table className="specs-table"><tbody><tr><td colSpan="2" className="specs-val">暂无参数</td></tr></tbody></table></div>
)}
</div>
</div>
)}
{/* <button className="pd-list-item" onClick={() => setPromoOpen(!promoOpen)}>
<span>活动说明</span>
<span>{promoOpen ? 'v' : ''}</span>
</button> */}
{promoOpen && <div className="pd-list-body"></div>}
<button className="pd-list-item" onClick={() => setFaqOpen(!faqOpen)}>
<span>常见问题</span>
<span>{faqOpen ? 'v' : ''}</span>
</button>
{faqOpen && <div className="pd-list-body"></div>}
<button className="pd-list-item" onClick={() => setReviewOpen(!reviewOpen)}>
<span>资料下载</span>
<span>{reviewOpen ? 'v' : ''}</span>
</button>
{reviewOpen && (
<div className="pd-list-body">
<div className="docs-body">
<DocsList series={series} sub={sub} />
</div>
</div>
)}
</div>
</div>
<div className="docs-section">
<div className="section-narrow">
<div className="section-tag tag-docs">资料下载</div>
<div className="docs-body">
<DocsList series={series} sub={sub} />
</div>
</div>
</div>
</div>
</div>
);
@@ -189,7 +234,7 @@ const DocsList = ({ series, sub }) => {
setLoading(true);
setError('');
const qs = new URLSearchParams({ series, sub }).toString();
const res = await fetch(`http://localhost:3001/api/docs/list?${qs}`);
const res = await fetch(`/api/docs/list?${qs}`);
const data = await res.json();
if (data.success) {
setDocs(data.data || []);
@@ -214,11 +259,11 @@ const DocsList = ({ series, sub }) => {
<ul className="docs-list">
{docs.map((d) => (
<li key={d.url} className="docs-item">
<a href={`http://localhost:3001${d.url}`} target="_blank" rel="noreferrer" className="docs-link">
<a href={`${d.url}`} target="_blank" rel="noreferrer" className="docs-link">
{d.name}
</a>
<span className="docs-size">{(d.size / 1024 / 1024).toFixed(2)}MB</span>
<a href={`http://localhost:3001${d.url}`} download className="docs-download">下载</a>
<a href={`${d.url}`} download className="docs-download">下载</a>
</li>
))}
</ul>