fix: 3D Show
This commit is contained in:
232
frontend/src/pages/ProductDetail.jsx
Normal file
232
frontend/src/pages/ProductDetail.jsx
Normal file
@@ -0,0 +1,232 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Button, Row, Col, Tag, Statistic, Modal, Form, Input, InputNumber, message, Spin, Descriptions } from 'antd';
|
||||
import { ShoppingCartOutlined, SafetyCertificateOutlined, ThunderboltOutlined, EyeOutlined, StarOutlined } from '@ant-design/icons';
|
||||
import { getConfigs, createOrder } from '../api';
|
||||
import ModelViewer from '../components/ModelViewer';
|
||||
import './ProductDetail.css';
|
||||
|
||||
const ProductDetail = () => {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [product, setProduct] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const refCode = searchParams.get('ref');
|
||||
|
||||
useEffect(() => {
|
||||
fetchProduct();
|
||||
}, [id]);
|
||||
|
||||
const fetchProduct = async () => {
|
||||
try {
|
||||
const response = await getConfigs();
|
||||
const found = response.data.find(p => String(p.id) === id);
|
||||
if (found) {
|
||||
setProduct(found);
|
||||
} else {
|
||||
message.error('未找到该产品');
|
||||
navigate('/');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch product:', error);
|
||||
message.error('加载失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBuy = async (values) => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const orderData = {
|
||||
config: product.id,
|
||||
quantity: values.quantity,
|
||||
customer_name: values.customer_name,
|
||||
phone_number: values.phone_number,
|
||||
shipping_address: values.shipping_address,
|
||||
ref_code: refCode
|
||||
};
|
||||
const response = await createOrder(orderData);
|
||||
message.success('订单创建成功');
|
||||
navigate(`/payment/${response.data.id}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
message.error('创建订单失败,请检查填写信息');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getModelPaths = (p) => {
|
||||
if (!p) return null;
|
||||
const text = (p.name + p.description).toLowerCase();
|
||||
|
||||
if (text.includes('mini')) {
|
||||
return { obj: '/3dmimi/3D_PCB_V3-mini.obj', mtl: '/3dmimi/3D_PCB_V3-mini.mtl' };
|
||||
} else if (text.includes('v2')) {
|
||||
return { obj: '/3dV2/xiaoliangV2.obj', mtl: '/3dV2/xiaoliangV2.mtl' };
|
||||
} else if (text.includes('vision') || text.includes('视觉') || text.includes('camera')) {
|
||||
return { obj: '/3dmodo/xiaoliang1.obj', mtl: '/3dmodo/xiaoliang1.mtl' };
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const modelPaths = getModelPaths(product);
|
||||
|
||||
const renderIcon = (feature) => {
|
||||
if (feature.display_icon) {
|
||||
return <img src={feature.display_icon} alt={feature.title} style={{ width: 60, height: 60, objectFit: 'contain', marginBottom: 20 }} />;
|
||||
}
|
||||
|
||||
const iconProps = { style: { fontSize: 60, color: '#00b96b', marginBottom: 20 } };
|
||||
|
||||
switch(feature.icon_name) {
|
||||
case 'SafetyCertificate':
|
||||
return <SafetyCertificateOutlined {...iconProps} />;
|
||||
case 'Eye':
|
||||
return <EyeOutlined {...iconProps} style={{ ...iconProps.style, color: '#1890ff' }} />;
|
||||
case 'Thunderbolt':
|
||||
return <ThunderboltOutlined {...iconProps} style={{ ...iconProps.style, color: '#faad14' }} />;
|
||||
default:
|
||||
return <StarOutlined {...iconProps} />;
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <div style={{ padding: 50, textAlign: 'center' }}><Spin size="large" /></div>;
|
||||
if (!product) return null;
|
||||
|
||||
return (
|
||||
<div className="product-detail-container" style={{ paddingBottom: '60px' }}>
|
||||
{/* Hero Section */}
|
||||
<Row gutter={40} align="middle" style={{ minHeight: '60vh' }}>
|
||||
<Col xs={24} md={12}>
|
||||
<div style={{
|
||||
height: 400,
|
||||
background: 'radial-gradient(circle, #2a2a2a 0%, #000 100%)',
|
||||
borderRadius: 20,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
border: '1px solid #333',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{modelPaths ? (
|
||||
<ModelViewer objPath={modelPaths.obj} mtlPath={modelPaths.mtl} />
|
||||
) : (
|
||||
<ThunderboltOutlined style={{ fontSize: 120, color: '#00b96b' }} />
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
<h1 style={{ fontSize: 48, fontWeight: 'bold', color: '#fff' }}>{product.name}</h1>
|
||||
<p style={{ fontSize: 20, color: '#888', margin: '20px 0' }}>{product.description}</p>
|
||||
|
||||
<div style={{ marginBottom: 30 }}>
|
||||
<span className="spec-tag">{product.chip_type}</span>
|
||||
{product.has_camera && <span className="spec-tag">高清摄像头</span>}
|
||||
{product.has_microphone && <span className="spec-tag">阵列麦克风</span>}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 20, marginBottom: 40 }}>
|
||||
<Statistic title="售价" value={product.price} prefix="¥" valueStyle={{ color: '#00b96b', fontSize: 36 }} titleStyle={{ color: '#888' }} />
|
||||
</div>
|
||||
|
||||
<Button type="primary" size="large" icon={<ShoppingCartOutlined />} onClick={() => setIsModalOpen(true)} style={{ height: 50, padding: '0 40px', fontSize: 18 }}>
|
||||
立即购买
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Feature Section */}
|
||||
<div style={{ marginTop: 100 }}>
|
||||
{product.features && product.features.length > 0 ? (
|
||||
product.features.map((feature, index) => (
|
||||
<div className="feature-section" key={index}>
|
||||
{renderIcon(feature)}
|
||||
<div className="feature-title">{feature.title}</div>
|
||||
<div className="feature-desc">{feature.description}</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
// Fallback content if no features are configured
|
||||
<>
|
||||
<div className="feature-section">
|
||||
<SafetyCertificateOutlined style={{ fontSize: 60, color: '#00b96b', marginBottom: 20 }} />
|
||||
<div className="feature-title">工业级安全标准</div>
|
||||
<div className="feature-desc">
|
||||
采用军工级加密芯片,保障您的数据隐私安全。无论是边缘计算还是云端同步,全程加密传输,让 AI 应用无后顾之忧。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="feature-section">
|
||||
<EyeOutlined style={{ fontSize: 60, color: '#1890ff', marginBottom: 20 }} />
|
||||
<div className="feature-title">超清视觉感知</div>
|
||||
<div className="feature-desc">
|
||||
搭载 4K 高清摄像头与 AI 视觉算法,实时捕捉每一个细节。支持人脸识别、物体检测、姿态分析等多种视觉任务。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="feature-section">
|
||||
<ThunderboltOutlined style={{ fontSize: 60, color: '#faad14', marginBottom: 20 }} />
|
||||
<div className="feature-title">极致性能释放</div>
|
||||
<div className="feature-desc">
|
||||
{product.chip_type} 强劲核心,提供高达 XX TOPS 的算力支持。低功耗设计,满足 24 小时全天候运行需求。
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{product.display_detail_image ? (
|
||||
<div style={{ margin: '60px 0', width: '100%', overflow: 'hidden', borderRadius: 12 }}>
|
||||
<img src={product.display_detail_image} alt="产品详情" style={{ width: '100%', display: 'block' }} />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ margin: '60px 0', height: 800, background: '#111', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#333', fontSize: 24, border: '1px dashed #333' }}>
|
||||
产品详情长图展示区域 (请在后台配置)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Order Modal */}
|
||||
<Modal
|
||||
title="填写收货信息"
|
||||
open={isModalOpen}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
footer={null}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleBuy}
|
||||
initialValues={{ quantity: 1 }}
|
||||
>
|
||||
<Form.Item label="购买数量" name="quantity" rules={[{ required: true }]}>
|
||||
<InputNumber min={1} max={100} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item label="收货人姓名" name="customer_name" rules={[{ required: true, message: '请输入姓名' }]}>
|
||||
<Input placeholder="张三" />
|
||||
</Form.Item>
|
||||
<Form.Item label="联系电话" name="phone_number" rules={[{ required: true, message: '请输入电话' }]}>
|
||||
<Input placeholder="13800000000" />
|
||||
</Form.Item>
|
||||
<Form.Item label="收货地址" name="shipping_address" rules={[{ required: true, message: '请输入地址' }]}>
|
||||
<Input.TextArea rows={3} placeholder="北京市..." />
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, marginTop: 20 }}>
|
||||
<Button onClick={() => setIsModalOpen(false)}>取消</Button>
|
||||
<Button type="primary" htmlType="submit" loading={submitting}>提交订单</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductDetail;
|
||||
Reference in New Issue
Block a user