Files
department-web/src/pages/ProductDetail.js
2025-12-09 15:37:23 +08:00

316 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { useParams, useLocation } from 'react-router-dom';
import axios from 'axios';
import './ProductDetail.css';
const ProductDetail = () => {
const { series, sub, idx } = useParams();
const location = useLocation();
const product = location.state?.product;
const [images, setImages] = useState([]);
const [active, setActive] = useState(0);
const [specs, setSpecs] = useState([]);
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(false);
const [promoOpen, setPromoOpen] = useState(false);
const [faqOpen, setFaqOpen] = useState(false);
const [reviewOpen, setReviewOpen] = useState(false);
useEffect(() => {
const srcs = product
? (Array.isArray(product.images) && product.images.length
? product.images
: (product.image ? [product.image] : []))
: [];
setImages(srcs);
setActive(0);
}, [product]);
useEffect(() => {
const loadImages = async () => {
try {
const qs = new URLSearchParams({ series, sub }).toString();
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) => `${d.url}`));
}
} catch {}
};
loadImages();
}, [series, sub]);
useEffect(() => {
const loadSpecs = async () => {
try {
setSpecsLoading(true);
const qs = new URLSearchParams({ series, sub }).toString();
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(Object.fromEntries(data.data.map((_, i) => [i, false])));
} else {
setSections([{ title: '参数', rows: data.data }]);
setSpecs(data.data);
setExpanded({ 0: false });
}
}
} catch {}
finally {
setSpecsLoading(false);
}
};
loadSpecs();
}, [series, sub]);
useEffect(() => {
if (images.length <= 1) return;
const timer = setInterval(() => {
setActive((prev) => (prev + 1) % images.length);
}, 5000);
return () => clearInterval(timer);
}, [images.length]);
if (!product) {
return (
<div className="product-detail-page">
<div className="container">
<div className="error-state">未找到产品信息</div>
</div>
</div>
);
}
const prevSlide = () => setActive((prev) => (prev - 1 + images.length) % images.length);
const nextSlide = () => setActive((prev) => (prev + 1) % images.length);
return (
<div className="product-detail-page">
<div className="container">
<div className="pd-title">{product.name}</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="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>
</div>
);
};
const DocsList = ({ series, sub }) => {
const [docs, setDocs] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleDownload = async (d) => {
try {
const res = await axios.get(d.url, { responseType: 'blob' });
const blob = new Blob([res.data], { type: res.data.type || 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = d.name || (String(d.url).split('/').pop() || '下载文件');
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
} catch (e) {
setError('下载失败或文件不存在');
}
};
const handleOpen = async (d) => {
try {
const res = await axios.get(d.url, { responseType: 'blob' });
const blob = new Blob([res.data], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
setTimeout(() => URL.revokeObjectURL(url), 60000);
} catch (e) {
window.open(d.url, '_blank');
}
};
React.useEffect(() => {
const fetchDocs = async () => {
try {
setLoading(true);
setError('');
const qs = new URLSearchParams({ series, sub }).toString();
const res = await fetch(`/api/docs/list?${qs}`);
const data = await res.json();
if (data.success) {
setDocs(data.data || []);
} else {
setError('获取资料失败');
}
} catch (e) {
setError('网络错误');
} finally {
setLoading(false);
}
};
fetchDocs();
}, [series, sub]);
if (loading) return <div className="docs-loading">加载中...</div>;
if (error) return <div className="docs-error">{error}</div>;
if (!docs.length) return <div className="docs-empty">暂无资料</div>;
return (
<ul className="docs-list">
{docs.map((d) => (
<li key={d.url} className="docs-item">
{String(d.url || '').toLowerCase().endsWith('.pdf') ? (
<a
href={`${d.url}`}
target="_blank"
rel="noreferrer"
className="docs-link"
onClick={(e) => { e.preventDefault(); handleOpen(d); }}
>
{d.name}
</a>
) : (
<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>
<button type="button" className="docs-download" onClick={() => handleDownload(d)}>下载</button>
</li>
))}
</ul>
);
};
export default ProductDetail;