diff --git a/README.md b/README.md index ffe957c..35048e2 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,14 @@ qweasdzxc1 - `POST /api/payments/initiate/` - 发起支付 - `POST /api/payments/confirm/` - 确认支付 + +## 上传图片接口 不要乱传文件,造成oss存储费用增加 +### 上传硬件的3D文件(小智参数) zip压缩包,包含3文件和材质文件 +- `POST https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/market_page/hardware_xiaozhi/product_3D_image` - 上传3D文件 + +### 上传硬件的图片(小智参数) 单张图片 +- `POST https://tangledup-ai-staging.oss-cn-shanghai.aliyuncs.com/market_page/hardware_xiaozhi/product_image` - 上传图片 + ## 🎯 使用说明 ### 推广码功能 diff --git a/backend/manage.py b/backend/manage.py old mode 100755 new mode 100644 diff --git a/backend/shop/admin.py b/backend/shop/admin.py index 99780ed..dd1cb70 100644 --- a/backend/shop/admin.py +++ b/backend/shop/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from django.utils.html import format_html from django.db.models import Sum +from django import forms from unfold.admin import ModelAdmin, TabularInline from unfold.decorators import display from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, ARService, ProductFeature @@ -13,6 +14,37 @@ admin.site.site_header = "量迹AI硬件销售管理后台" admin.site.site_title = "量迹AI后台" admin.site.index_title = "欢迎使用量迹AI管理系统" +class ExternalUploadWidget(forms.URLInput): + def __init__(self, upload_url, accept='*', *args, **kwargs): + super().__init__(*args, **kwargs) + self.upload_url = upload_url + self.attrs.update({ + 'class': 'upload-url-input', + 'data-upload-url': upload_url, + 'data-accept': accept, + 'style': 'width: 100%; margin-bottom: 5px;', + 'readonly': 'readonly', + 'placeholder': '上传文件后自动生成URL' + }) + + class Media: + js = ('shop/js/admin_upload.js',) + +class ESP32ConfigAdminForm(forms.ModelForm): + class Meta: + model = ESP32Config + fields = '__all__' + widgets = { + 'static_image_url': ExternalUploadWidget( + upload_url='https://data.tangledup-ai.com/upload?folder=market_page%2Fhardware_xiaozhi%2Fproduct_static_image', + accept='image/*' + ), + 'model_3d_url': ExternalUploadWidget( + upload_url='https://data.tangledup-ai.com/upload?folder=market_page%2Fhardware_xiaozhi%2Fproduct_3D_image', + accept='.zip' + ), + } + class ProductFeatureInline(TabularInline): model = ProductFeature extra = 1 @@ -37,6 +69,7 @@ class WeChatPayConfigAdmin(ModelAdmin): @admin.register(ESP32Config) class ESP32ConfigAdmin(ModelAdmin): + form = ESP32ConfigAdminForm list_display = ('name', 'chip_type', 'price', 'has_camera', 'has_microphone') list_filter = ('chip_type', 'has_camera') search_fields = ('name', 'description') @@ -52,6 +85,10 @@ class ESP32ConfigAdmin(ModelAdmin): 'fields': ('detail_image', 'detail_image_url'), 'description': '图片上传和URL二选一,优先使用URL' }), + ('多媒体资源', { + 'fields': ('static_image_url', 'model_3d_url'), + 'description': '产品静态图和3D模型的外部链接' + }), ) @admin.register(Service) diff --git a/backend/shop/migrations/0009_esp32config_model_3d_url_and_more.py b/backend/shop/migrations/0009_esp32config_model_3d_url_and_more.py new file mode 100644 index 0000000..f8e6b01 --- /dev/null +++ b/backend/shop/migrations/0009_esp32config_model_3d_url_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.1 on 2026-02-02 11:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0008_service_delivery_content_service_delivery_time_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='esp32config', + name='model_3d_url', + field=models.URLField(blank=True, null=True, verbose_name='产品3D模型 (URL)'), + ), + migrations.AddField( + model_name='esp32config', + name='static_image_url', + field=models.URLField(blank=True, null=True, verbose_name='产品静态图 (URL)'), + ), + ] diff --git a/backend/shop/migrations/0010_alter_esp32config_model_3d_url.py b/backend/shop/migrations/0010_alter_esp32config_model_3d_url.py new file mode 100644 index 0000000..e24695f --- /dev/null +++ b/backend/shop/migrations/0010_alter_esp32config_model_3d_url.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.1 on 2026-02-02 12:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0009_esp32config_model_3d_url_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='esp32config', + name='model_3d_url', + field=models.URLField(blank=True, help_text='请上传包含 .obj 模型文件和 .mtl 材质文件的 .zip 压缩包', null=True, verbose_name='产品3D模型 (URL)'), + ), + ] diff --git a/backend/shop/models.py b/backend/shop/models.py index b7395d0..4d0a633 100644 --- a/backend/shop/models.py +++ b/backend/shop/models.py @@ -19,6 +19,8 @@ class ESP32Config(models.Model): description = models.TextField(verbose_name="描述", blank=True) detail_image = models.ImageField(upload_to='products/details/', blank=True, null=True, verbose_name="详情页长图 (上传)") detail_image_url = models.URLField(blank=True, null=True, verbose_name="详情页长图 (URL)", help_text="如果填写了URL,将优先使用URL") + static_image_url = models.URLField(blank=True, null=True, verbose_name="产品静态图 (URL)") + model_3d_url = models.URLField(blank=True, null=True, verbose_name="产品3D模型 (URL)", help_text="请上传包含 .obj 模型文件和 .mtl 材质文件的 .zip 压缩包") def __str__(self): return f"{self.name} - ¥{self.price}" diff --git a/frontend/index.html b/frontend/index.html index 577ec1d..f80eaeb 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ - + - frontend + Quant Speed
diff --git a/frontend/package.json b/frontend/package.json index 5757c39..0712903 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "antd": "^6.2.2", "axios": "^1.13.4", "framer-motion": "^12.29.2", + "jszip": "^3.10.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0", diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 6429f71..59c82ae 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -28,9 +28,14 @@ const Layout = ({ children }) => { icon: , label: 'AR 体验', }, + { + key: 'more', + label: '...', + }, ]; const handleMenuClick = (key) => { + if (key === 'more') return; navigate(key); setMobileMenuOpen(false); }; @@ -57,17 +62,18 @@ const Layout = ({ children }) => { zIndex: 1000, width: '100%', padding: 0, - background: 'rgba(0, 0, 0, 0.5)', - backdropFilter: 'blur(10px)', + background: 'rgba(0, 0, 0, 0.7)', + backdropFilter: 'blur(20px)', borderBottom: '1px solid rgba(255, 255, 255, 0.1)', display: 'flex', - justifyContent: 'center' + height: '72px', + lineHeight: '72px', + boxShadow: '0 4px 30px rgba(0, 0, 0, 0.5)' }} >
{ }} onClick={() => navigate('/')} > - Quant-Speed Logo + Quant Speed Logo {/* Desktop Menu */} -
+
handleMenuClick(e.key)} - style={{ background: 'transparent', borderBottom: 'none', minWidth: 300 }} + style={{ + background: 'transparent', + borderBottom: 'none', + display: 'flex', + justifyContent: 'flex-end', + minWidth: '400px' + }} />
diff --git a/frontend/src/pages/Home.css b/frontend/src/pages/Home.css index 4c3b6ef..095bb0f 100644 --- a/frontend/src/pages/Home.css +++ b/frontend/src/pages/Home.css @@ -3,14 +3,22 @@ border: 1px solid #303030 !important; transition: all 0.3s ease; cursor: pointer; + box-shadow: none !important; /* 强制移除默认阴影 */ + overflow: hidden; /* 确保子元素不会溢出产生黑边 */ + outline: none; } .tech-card:hover { border-color: #00b96b !important; - box-shadow: 0 0 15px rgba(0, 185, 107, 0.3); + box-shadow: 0 0 20px rgba(0, 185, 107, 0.4) !important; /* 增强悬停发光 */ transform: translateY(-5px); } +.tech-card .ant-card-body { + border-top: none !important; + box-shadow: none !important; +} + .tech-card-title { color: #fff; font-size: 18px; @@ -23,3 +31,48 @@ font-size: 20px; font-weight: bold; } + +.product-scroll-container { + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + padding: 30px 20px; /* 增加左右内边距,为悬停缩放和投影留出空间 */ + margin: 0 -20px; /* 使用负外边距抵消内边距,使滚动条能延伸到版心边缘 */ + width: calc(100% + 40px); +} + +/* 自定义滚动条 */ +.product-scroll-container::-webkit-scrollbar { + height: 6px; +} + +.product-scroll-container::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 3px; + margin: 0 20px; /* 让滚动条轨道在版心内显示 */ +} + +.product-scroll-container::-webkit-scrollbar-thumb { + background: rgba(0, 185, 107, 0.2); + border-radius: 3px; + transition: all 0.3s; +} + +.product-scroll-container::-webkit-scrollbar-thumb:hover { + background: rgba(0, 185, 107, 0.5); +} + +/* 布局对齐 */ +.product-scroll-container .ant-row { + margin-left: 0 !important; + margin-right: 0 !important; + padding: 0; + display: flex; + flex-wrap: nowrap; + justify-content: flex-start; +} + +.product-scroll-container .ant-col { + flex: 0 0 320px; + padding: 0 12px; +} diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 71774e5..94253e8 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -12,6 +12,7 @@ const Home = () => { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [typedText, setTypedText] = useState(''); + const [isTypingComplete, setIsTypingComplete] = useState(false); const fullText = "未来已来 AI 核心驱动"; const navigate = useNavigate(); @@ -23,6 +24,7 @@ const Home = () => { setTypedText(fullText.slice(0, i)); if (i >= fullText.length) { clearInterval(typingInterval); + setIsTypingComplete(true); } }, 150); @@ -56,7 +58,6 @@ const Home = () => { scale: 1.05, rotateX: 5, rotateY: 5, - boxShadow: "0px 10px 30px rgba(0, 185, 107, 0.4)", transition: { duration: 0.3 } } }; @@ -71,7 +72,7 @@ const Home = () => { return (
-
+
{ > { /> - <span className="neon-text-green">{typedText}</span><span className="cursor-blink">|</span> + <span className="neon-text-green">{typedText}</span> + {!isTypingComplete && <span className="cursor-blink">|</span>} {
- - {products.map((product, index) => ( - - +
+ + {products.map((product, index) => ( + + { justifyContent: 'center', alignItems: 'center', color: '#444', - borderBottom: '1px solid rgba(255,255,255,0.05)' + borderBottom: '1px solid rgba(255,255,255,0.05)', + overflow: 'hidden' }}> - - - + {product.static_image_url ? ( + {product.name} + ) : ( + + + + )}
} onClick={() => navigate(`/product/${product.id}`)} @@ -144,9 +156,10 @@ const Home = () => {
{product.description}
-
- {product.chip_type} - {product.has_camera && Camera} +
+ {product.chip_type} + {product.has_camera && Camera} + {product.has_microphone && Mic}
¥{product.price}
@@ -157,6 +170,7 @@ const Home = () => { ))} +