fix: 3D Show

This commit is contained in:
xiaoma
2026-02-02 19:10:34 +08:00
commit b8024da3dc
61 changed files with 4123 additions and 0 deletions

View 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;