diff --git a/backend/check_urls.py b/backend/check_urls.py
new file mode 100644
index 0000000..63e303d
--- /dev/null
+++ b/backend/check_urls.py
@@ -0,0 +1,30 @@
+import os
+import django
+from django.urls import reverse
+from django.conf import settings
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
+django.setup()
+
+links = [
+ "admin:shop_wechatuser_changelist",
+ "admin:shop_salesperson_changelist",
+ "admin:shop_distributor_changelist",
+ "admin:shop_esp32config_changelist",
+ "admin:shop_service_changelist",
+ "admin:shop_arservice_changelist",
+ "admin:shop_order_changelist",
+ "admin:shop_serviceorder_changelist",
+ "admin:shop_withdrawal_changelist",
+ "admin:shop_commissionlog_changelist",
+ "admin:shop_wechatpayconfig_changelist",
+ "admin:auth_user_changelist",
+]
+
+print("Checking URL patterns...")
+for link in links:
+ try:
+ url = reverse(link)
+ print(f"[OK] {link} -> {url}")
+ except Exception as e:
+ print(f"[ERROR] {link}: {e}")
diff --git a/backend/config/__pycache__/settings.cpython-312.pyc b/backend/config/__pycache__/settings.cpython-312.pyc
index 340c2b4..67a96a2 100644
Binary files a/backend/config/__pycache__/settings.cpython-312.pyc and b/backend/config/__pycache__/settings.cpython-312.pyc differ
diff --git a/backend/config/__pycache__/settings.cpython-313.pyc b/backend/config/__pycache__/settings.cpython-313.pyc
index cb9f72f..b372ee5 100644
Binary files a/backend/config/__pycache__/settings.cpython-313.pyc and b/backend/config/__pycache__/settings.cpython-313.pyc differ
diff --git a/backend/config/settings.py b/backend/config/settings.py
index 72f7f33..b2702bb 100644
--- a/backend/config/settings.py
+++ b/backend/config/settings.py
@@ -170,26 +170,118 @@ SPECTACULAR_SETTINGS = {
'REDOC_DIST': 'SIDECAR',
}
+from django.urls import reverse_lazy
+
# django-unfold配置
UNFOLD = {
- "SITE_TITLE": "科技公司产品管理",
- "SITE_HEADER": "科技公司产品购买系统",
+ "SITE_TITLE": "量迹AI后台",
+ "SITE_HEADER": "量迹AI科技硬件/服务商场后台",
"SITE_URL": "/",
"COLORS": {
"primary": {
- "50": "rgb(240 249 255)",
- "100": "rgb(224 242 254)",
- "200": "rgb(186 230 253)",
- "300": "rgb(125 211 252)",
- "400": "rgb(56 189 248)",
- "500": "rgb(14 165 233)",
- "600": "rgb(2 132 199)",
- "700": "rgb(3 105 161)",
- "800": "rgb(7 89 133)",
- "900": "rgb(12 74 110)",
- "950": "rgb(8 47 73)",
+ "50": "rgb(236 254 255)",
+ "100": "rgb(207 250 254)",
+ "200": "rgb(165 243 252)",
+ "300": "rgb(103 232 249)",
+ "400": "rgb(34 211 238)",
+ "500": "rgb(6 182 212)",
+ "600": "rgb(8 145 178)",
+ "700": "rgb(14 116 144)",
+ "800": "rgb(21 94 117)",
+ "900": "rgb(22 78 99)",
+ "950": "rgb(8 51 68)",
},
},
+ "SIDEBAR": {
+ "show_search": True,
+ "show_all_applications": False,
+ "navigation": [
+ {
+ "title": "用户管理",
+ "separator": True,
+ "items": [
+ {
+ "title": "微信用户",
+ "icon": "people",
+ "link": reverse_lazy("admin:shop_wechatuser_changelist"),
+ },
+ {
+ "title": "分销员管理",
+ "icon": "supervisor_account",
+ "link": reverse_lazy("admin:shop_salesperson_changelist"),
+ },
+ {
+ "title": "小程序分销员",
+ "icon": "groups",
+ "link": reverse_lazy("admin:shop_distributor_changelist"),
+ },
+ ],
+ },
+ {
+ "title": "商品管理",
+ "separator": True,
+ "items": [
+ {
+ "title": "硬件配置 (小智参数)",
+ "icon": "hardware",
+ "link": reverse_lazy("admin:shop_esp32config_changelist"),
+ },
+ {
+ "title": "AI服务",
+ "icon": "smart_toy",
+ "link": reverse_lazy("admin:shop_service_changelist"),
+ },
+ {
+ "title": "AR体验",
+ "icon": "view_in_ar",
+ "link": reverse_lazy("admin:shop_arservice_changelist"),
+ },
+ ],
+ },
+ {
+ "title": "交易管理",
+ "separator": True,
+ "items": [
+ {
+ "title": "订单列表",
+ "icon": "shopping_cart",
+ "link": reverse_lazy("admin:shop_order_changelist"),
+ },
+ {
+ "title": "服务订单",
+ "icon": "assignment",
+ "link": reverse_lazy("admin:shop_serviceorder_changelist"),
+ },
+ {
+ "title": "提现管理",
+ "icon": "account_balance_wallet",
+ "link": reverse_lazy("admin:shop_withdrawal_changelist"),
+ },
+ {
+ "title": "佣金记录",
+ "icon": "monetization_on",
+ "link": reverse_lazy("admin:shop_commissionlog_changelist"),
+ },
+ ],
+ },
+ {
+ "title": "系统配置",
+ "separator": True,
+ "items": [
+ {
+ "title": "微信支付配置",
+ "icon": "payment",
+ "link": reverse_lazy("admin:shop_wechatpayconfig_changelist"),
+ },
+ {
+ "title": "用户认证",
+ "icon": "security",
+ "link": reverse_lazy("admin:auth_user_changelist"),
+ },
+ ],
+ },
+ ],
+ },
}
# 重新启用自动补齐斜杠,方便 Admin 使用
diff --git a/backend/shop/__pycache__/admin.cpython-312.pyc b/backend/shop/__pycache__/admin.cpython-312.pyc
index 092085d..d98e11c 100644
Binary files a/backend/shop/__pycache__/admin.cpython-312.pyc and b/backend/shop/__pycache__/admin.cpython-312.pyc differ
diff --git a/backend/shop/__pycache__/admin.cpython-313.pyc b/backend/shop/__pycache__/admin.cpython-313.pyc
index 85b64b3..b314ad6 100644
Binary files a/backend/shop/__pycache__/admin.cpython-313.pyc and b/backend/shop/__pycache__/admin.cpython-313.pyc differ
diff --git a/backend/shop/__pycache__/serializers.cpython-313.pyc b/backend/shop/__pycache__/serializers.cpython-313.pyc
index bbad70a..0164209 100644
Binary files a/backend/shop/__pycache__/serializers.cpython-313.pyc and b/backend/shop/__pycache__/serializers.cpython-313.pyc differ
diff --git a/backend/shop/__pycache__/urls.cpython-313.pyc b/backend/shop/__pycache__/urls.cpython-313.pyc
index 7b6b679..a251174 100644
Binary files a/backend/shop/__pycache__/urls.cpython-313.pyc and b/backend/shop/__pycache__/urls.cpython-313.pyc differ
diff --git a/backend/shop/__pycache__/views.cpython-313.pyc b/backend/shop/__pycache__/views.cpython-313.pyc
index add1d4f..8cd5bca 100644
Binary files a/backend/shop/__pycache__/views.cpython-313.pyc and b/backend/shop/__pycache__/views.cpython-313.pyc differ
diff --git a/backend/shop/admin.py b/backend/shop/admin.py
index 4125d20..fc08102 100644
--- a/backend/shop/admin.py
+++ b/backend/shop/admin.py
@@ -4,13 +4,13 @@ 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, CommissionLog
+from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, ARService, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder
import qrcode
from io import BytesIO
import base64
# 自定义后台标题
-admin.site.site_header = "量迹AI硬件销售管理后台"
+admin.site.site_header = "量迹AI科技硬件/服务商场后台"
admin.site.site_title = "量迹AI后台"
admin.site.index_title = "欢迎使用量迹AI管理系统"
@@ -122,6 +122,25 @@ class ServiceAdmin(ModelAdmin):
}),
)
+@admin.register(ServiceOrder)
+class ServiceOrderAdmin(ModelAdmin):
+ list_display = ('id', 'customer_name', 'service', 'total_price', 'status', 'salesperson', 'created_at')
+ list_filter = ('status', 'service', 'salesperson', 'created_at')
+ search_fields = ('id', 'customer_name', 'phone_number', 'email')
+ readonly_fields = ('total_price', 'created_at', 'updated_at')
+
+ fieldsets = (
+ ('订单信息', {
+ 'fields': ('service', 'status', 'total_price', 'created_at')
+ }),
+ ('客户信息', {
+ 'fields': ('customer_name', 'company_name', 'phone_number', 'email', 'requirements')
+ }),
+ ('销售归属', {
+ 'fields': ('salesperson',)
+ }),
+ )
+
@admin.register(ARService)
class ARServiceAdmin(ModelAdmin):
list_display = ('title', 'created_at')
@@ -251,3 +270,84 @@ class OrderAdmin(ModelAdmin):
'fields': ('wechat_trade_no',)
}),
)
+
+@admin.register(WeChatUser)
+class WeChatUserAdmin(ModelAdmin):
+ list_display = ('nickname', 'avatar_display', 'gender_display', 'province', 'city', 'created_at')
+ search_fields = ('nickname', 'openid')
+ list_filter = ('gender', 'province', 'city', 'created_at')
+ readonly_fields = ('openid', 'unionid', 'session_key', 'created_at', 'updated_at')
+
+ def avatar_display(self, obj):
+ if obj.avatar_url:
+ return format_html('
', obj.avatar_url)
+ return "暂无"
+ avatar_display.short_description = "头像"
+
+ def gender_display(self, obj):
+ choices = {0: '未知', 1: '男', 2: '女'}
+ return choices.get(obj.gender, '未知')
+ gender_display.short_description = "性别"
+
+ fieldsets = (
+ ('基本信息', {
+ 'fields': ('user', 'nickname', 'avatar_url', 'gender')
+ }),
+ ('位置信息', {
+ 'fields': ('country', 'province', 'city')
+ }),
+ ('认证信息', {
+ 'fields': ('openid', 'unionid', 'session_key'),
+ 'classes': ('collapse',)
+ }),
+ ('时间信息', {
+ 'fields': ('created_at', 'updated_at')
+ }),
+ )
+
+@admin.register(Distributor)
+class DistributorAdmin(ModelAdmin):
+ list_display = ('get_nickname', 'level', 'status', 'total_earnings', 'withdrawable_balance', 'invite_code', 'created_at')
+ search_fields = ('user__nickname', 'invite_code')
+ list_filter = ('status', 'level', 'created_at')
+ readonly_fields = ('total_earnings', 'withdrawable_balance', 'qr_code_url', 'created_at', 'updated_at')
+ autocomplete_fields = ['user', 'parent']
+
+ def get_nickname(self, obj):
+ return obj.user.nickname
+ get_nickname.short_description = "微信昵称"
+ get_nickname.admin_order_field = 'user__nickname'
+
+ fieldsets = (
+ ('分销员信息', {
+ 'fields': ('user', 'parent', 'level', 'status')
+ }),
+ ('收益概览', {
+ 'fields': ('commission_rate', 'total_earnings', 'withdrawable_balance')
+ }),
+ ('推广信息', {
+ 'fields': ('invite_code', 'qr_code_url')
+ }),
+ ('时间信息', {
+ 'fields': ('created_at', 'updated_at')
+ }),
+ )
+
+@admin.register(Withdrawal)
+class WithdrawalAdmin(ModelAdmin):
+ list_display = ('get_distributor', 'amount', 'status', 'created_at')
+ list_filter = ('status', 'created_at')
+ search_fields = ('distributor__user__nickname',)
+
+ def get_distributor(self, obj):
+ return obj.distributor.user.nickname
+ get_distributor.short_description = "分销员"
+
+ fieldsets = (
+ ('提现详情', {
+ 'fields': ('distributor', 'amount', 'status', 'remark')
+ }),
+ ('时间信息', {
+ 'fields': ('created_at', 'updated_at')
+ }),
+ )
diff --git a/miniprogram/src/assets/logo.svg b/miniprogram/src/assets/logo.svg
new file mode 100644
index 0000000..e2c1f43
--- /dev/null
+++ b/miniprogram/src/assets/logo.svg
@@ -0,0 +1,6 @@
+
diff --git a/miniprogram/src/components/ParticleBackground/index.scss b/miniprogram/src/components/ParticleBackground/index.scss
new file mode 100644
index 0000000..0babd47
--- /dev/null
+++ b/miniprogram/src/components/ParticleBackground/index.scss
@@ -0,0 +1,10 @@
+.particle-canvas {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+ pointer-events: none;
+ background: #000;
+}
diff --git a/miniprogram/src/components/ParticleBackground/index.tsx b/miniprogram/src/components/ParticleBackground/index.tsx
new file mode 100644
index 0000000..5db0a0d
--- /dev/null
+++ b/miniprogram/src/components/ParticleBackground/index.tsx
@@ -0,0 +1,175 @@
+import { Canvas, View } from '@tarojs/components'
+import Taro, { useReady, useUnload } from '@tarojs/taro'
+import { useRef } from 'react'
+import './index.scss'
+
+export default function ParticleBackground() {
+ const canvasRef = useRef(null)
+ const animationRef = useRef(null)
+
+ useReady(() => {
+ const query = Taro.createSelectorQuery()
+ query.select('#particle-canvas')
+ .fields({ node: true, size: true })
+ .exec((res) => {
+ if (!res[0]) return
+ const canvas = res[0].node
+ const ctx = canvas.getContext('2d')
+ const dpr = Taro.getSystemInfoSync().pixelRatio
+
+ canvas.width = res[0].width * dpr
+ canvas.height = res[0].height * dpr
+ ctx.scale(dpr, dpr)
+
+ const width = res[0].width
+ const height = res[0].height
+
+ // Init particles
+ const particles: any[] = []
+ const particleCount = 40 // Reduced for mobile performance
+ const meteors: any[] = []
+ const meteorCount = 4
+
+ class Particle {
+ x: number
+ y: number
+ vx: number
+ vy: number
+ size: number
+ color: string
+ constructor() {
+ this.x = Math.random() * width
+ this.y = Math.random() * height
+ this.vx = (Math.random() - 0.5) * 0.5
+ this.vy = (Math.random() - 0.5) * 0.5
+ this.size = Math.random() * 2
+ this.color = Math.random() > 0.5 ? 'rgba(0, 185, 107, ' : 'rgba(0, 240, 255, '
+ }
+ update() {
+ this.x += this.vx
+ this.y += this.vy
+ if (this.x < 0 || this.x > width) this.vx *= -1
+ if (this.y < 0 || this.y > height) this.vy *= -1
+ }
+ draw() {
+ ctx.beginPath()
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
+ ctx.fillStyle = this.color + (0.5 + Math.random() * 0.5) + ')'
+ ctx.fill()
+ }
+ }
+
+ class Meteor {
+ x: number
+ y: number
+ vx: number
+ vy: number
+ len: number
+ color: string
+ opacity: number
+ maxOpacity: number
+ wait: number
+ constructor() {
+ this.x = 0
+ this.y = 0
+ this.vx = 0
+ this.vy = 0
+ this.len = 0
+ this.color = ''
+ this.opacity = 0
+ this.maxOpacity = 0
+ this.wait = 0
+ this.reset()
+ }
+ reset() {
+ this.x = Math.random() * width * 1.5
+ this.y = Math.random() * -height
+ this.vx = -(Math.random() * 3 + 3)
+ this.vy = Math.random() * 3 + 3
+ this.len = Math.random() * 100 + 100
+ this.color = Math.random() > 0.5 ? 'rgba(0, 185, 107, ' : 'rgba(0, 240, 255, '
+ this.opacity = 0
+ this.maxOpacity = Math.random() * 0.5 + 0.2
+ this.wait = Math.random() * 200
+ }
+ update() {
+ if (this.wait > 0) {
+ this.wait--
+ return
+ }
+ this.x += this.vx
+ this.y += this.vy
+ if (this.opacity < this.maxOpacity) this.opacity += 0.02
+ if (this.x < -this.len || this.y > height + this.len) this.reset()
+ }
+ draw() {
+ if (this.wait > 0) return
+ const tailX = this.x - this.vx * (this.len / 15)
+ const tailY = this.y - this.vy * (this.len / 15)
+
+ const gradient = ctx.createLinearGradient(this.x, this.y, tailX, tailY)
+ gradient.addColorStop(0, this.color + this.opacity + ')')
+ gradient.addColorStop(1, this.color + '0)')
+
+ ctx.save()
+ ctx.beginPath()
+ ctx.strokeStyle = gradient
+ ctx.lineWidth = 2
+ ctx.lineCap = 'round'
+ ctx.moveTo(this.x, this.y)
+ ctx.lineTo(tailX, tailY)
+ ctx.stroke()
+ ctx.restore()
+ }
+ }
+
+ for (let i = 0; i < particleCount; i++) particles.push(new Particle())
+ for (let i = 0; i < meteorCount; i++) meteors.push(new Meteor())
+
+ const animate = () => {
+ ctx.clearRect(0, 0, width, height)
+
+ // Draw Meteors
+ meteors.forEach(m => {
+ m.update()
+ m.draw()
+ })
+
+ // Draw Lines
+ ctx.lineWidth = 0.5
+ for (let i = 0; i < particleCount; i++) {
+ for (let j = i; j < particleCount; j++) {
+ const dx = particles[i].x - particles[j].x
+ const dy = particles[i].y - particles[j].y
+ const dist = Math.sqrt(dx * dx + dy * dy)
+ if (dist < 80) { // Reduced distance for mobile
+ ctx.beginPath()
+ ctx.strokeStyle = `rgba(100, 255, 218, ${1 - dist / 80})`
+ ctx.moveTo(particles[i].x, particles[i].y)
+ ctx.lineTo(particles[j].x, particles[j].y)
+ ctx.stroke()
+ }
+ }
+ }
+
+ // Draw Particles
+ particles.forEach(p => {
+ p.update()
+ p.draw()
+ })
+
+ canvas.requestAnimationFrame(animate)
+ }
+
+ animate()
+ })
+ })
+
+ return (
+
+ )
+}
diff --git a/miniprogram/src/pages/goods/detail.scss b/miniprogram/src/pages/goods/detail.scss
index 9ea9fbb..fd9973f 100644
--- a/miniprogram/src/pages/goods/detail.scss
+++ b/miniprogram/src/pages/goods/detail.scss
@@ -1,138 +1,223 @@
.page-container {
height: 100vh;
- display: flex;
- flex-direction: column;
background-color: #000;
color: #fff;
+ position: relative;
+}
+
+.loading-screen, .error-screen {
+ height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #666;
}
.content {
- flex: 1;
- overflow-y: auto;
+ height: 100vh;
+ background: #000;
}
-.detail-img {
- width: 100%;
- display: block;
+.glass-panel {
+ background: rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
-.info-section {
- padding: 20px;
-}
-
-.title {
- font-size: 24px;
- font-weight: bold;
- color: #00f0ff;
- display: block;
- margin-bottom: 10px;
-}
-
-.price {
- font-size: 28px;
- color: #00b96b;
- font-weight: bold;
- display: block;
- margin-bottom: 20px;
-}
-
-.specs {
- display: flex;
- background: rgba(255,255,255,0.05);
- border-radius: 8px;
- padding: 15px;
+.hero-section {
+ position: relative;
margin-bottom: 20px;
- .spec-item {
- flex: 1;
- text-align: center;
- border-right: 1px solid rgba(255,255,255,0.1);
-
- &:last-child {
- border-right: none;
- }
-
- .label {
- font-size: 12px;
- color: #888;
+ .image-container {
+ width: 100%;
+ min-height: 600px;
+ background: radial-gradient(circle at center, #1a1a1a, #000);
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .hero-img {
+ width: 100%;
display: block;
- margin-bottom: 5px;
+ }
+
+ .placeholder-box {
+ .icon-bolt { font-size: 100px; }
}
- .value {
- font-size: 14px;
+ .hero-overlay {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 60%;
+ background: linear-gradient(to top, #000 10%, transparent);
+ }
+ }
+
+ .hero-content {
+ padding: 0 30px;
+ margin-top: -100px; // Pull up over image
+ position: relative;
+ z-index: 2;
+
+ .hero-title {
+ font-size: 48px;
+ font-weight: 900;
color: #fff;
- font-weight: bold;
+ display: block;
+ margin-bottom: 15px;
+ text-shadow: 0 0 20px rgba(0,0,0,0.8);
+ }
+
+ .hero-desc {
+ font-size: 28px;
+ color: #ccc;
+ line-height: 1.5;
+ display: block;
+ margin-bottom: 25px;
+ text-shadow: 0 0 10px rgba(0,0,0,0.8);
+ }
+
+ .tags-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+
+ .tag {
+ padding: 8px 20px;
+ border-radius: 30px;
+ font-size: 24px;
+ backdrop-filter: blur(10px);
+
+ &.cyan { background: rgba(0, 240, 255, 0.15); color: #00f0ff; border: 1px solid rgba(0, 240, 255, 0.3); }
+ &.blue { background: rgba(59, 130, 246, 0.15); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.3); }
+ &.purple { background: rgba(168, 85, 247, 0.15); color: #c084fc; border: 1px solid rgba(168, 85, 247, 0.3); }
+ }
}
}
}
-.desc {
- margin-bottom: 20px;
+.stats-card {
+ margin: 0 30px 40px;
+ border-radius: 24px;
+ padding: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
- .section-title {
- font-size: 16px;
- color: #fff;
- margin-bottom: 10px;
- display: block;
- border-left: 3px solid #00f0ff;
- padding-left: 10px;
+ .stat-item {
+ text-align: center;
+ .stat-label { font-size: 24px; color: #888; display: block; margin-bottom: 10px; }
+ .stat-value { font-size: 36px; font-weight: bold; color: #fff; }
+ .price { color: #00b96b; text-shadow: 0 0 10px rgba(0, 185, 107, 0.3); }
+ .low-stock { color: #ff4d4f; }
}
+
+ .divider { width: 1px; height: 60px; background: rgba(255,255,255,0.1); }
+}
- .text {
- font-size: 14px;
- color: #ccc;
- line-height: 1.6;
+.features-section {
+ padding: 0 30px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ margin-bottom: 40px;
+
+ .feature-card {
+ padding: 30px;
+ border-radius: 20px;
+ display: flex;
+ align-items: flex-start;
+
+ .feature-icon-box {
+ width: 80px;
+ height: 80px;
+ margin-right: 25px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255,255,255,0.05);
+ border-radius: 16px;
+
+ .f-icon { font-size: 40px; color: #00f0ff; }
+ .f-icon-img { width: 50px; height: 50px; }
+ }
+
+ .feature-text {
+ flex: 1;
+ .f-title { font-size: 30px; font-weight: bold; color: #fff; margin-bottom: 10px; display: block; }
+ .f-desc { font-size: 24px; color: #aaa; line-height: 1.5; }
+ }
}
}
-.feature-item {
- margin-bottom: 15px;
-
- .f-title {
- font-size: 15px;
- color: #00f0ff;
- margin-bottom: 5px;
- display: block;
- }
-
- .f-desc {
- font-size: 13px;
- color: #bbb;
- }
+.detail-image-section {
+ width: 100%;
+ margin-bottom: 40px;
+ .long-detail-img { width: 100%; display: block; }
}
+.footer-spacer { height: 160px; }
+
.bottom-bar {
- background: #111;
- padding: 10px 20px;
- border-top: 1px solid rgba(255,255,255,0.1);
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 20px 30px;
+ z-index: 100;
+ border-top-left-radius: 30px;
+ border-top-right-radius: 30px;
+ background: rgba(20, 20, 20, 0.95); // Darker for contrast
- .btn-container {
+ .action-row {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ height: 100px;
+
+ .cart-icon-btn {
display: flex;
- gap: 15px;
- }
-
- .btn-cart, .btn-buy {
- flex: 1;
- border: none;
- color: #fff;
- font-size: 16px;
- height: 44px;
- line-height: 44px;
- border-radius: 22px;
- margin: 0;
- }
-
- .btn-cart {
- background: #333;
- }
-
- .btn-buy {
- background: #00b96b;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 0 20px;
+
+ .icon { font-size: 40px; margin-bottom: 5px; }
+ .label { font-size: 20px; color: #888; }
+ }
+
+ .btn-add-cart, .btn-buy-now {
+ flex: 1;
+ height: 80px;
+ line-height: 80px;
+ border-radius: 40px;
+ font-size: 28px;
+ font-weight: bold;
+ border: none;
+ margin: 0;
+
+ &::after { border: none; }
+ }
+
+ .btn-add-cart {
+ background: rgba(255, 255, 255, 0.1);
+ color: #fff;
+ }
+
+ .btn-buy-now {
+ background: linear-gradient(90deg, #00b96b, #00f0ff);
+ color: #000;
+ box-shadow: 0 5px 20px rgba(0, 185, 107, 0.3);
+ }
}
}
.safe-area-bottom {
- padding-bottom: constant(safe-area-inset-bottom);
- padding-bottom: env(safe-area-inset-bottom);
+ padding-bottom: calc(20px + constant(safe-area-inset-bottom));
+ padding-bottom: calc(20px + env(safe-area-inset-bottom));
}
diff --git a/miniprogram/src/pages/goods/detail.tsx b/miniprogram/src/pages/goods/detail.tsx
index af85362..dfc6b67 100644
--- a/miniprogram/src/pages/goods/detail.tsx
+++ b/miniprogram/src/pages/goods/detail.tsx
@@ -8,6 +8,7 @@ export default function Detail() {
const router = useRouter()
const { id } = router.params
const [product, setProduct] = useState(null)
+ const [loading, setLoading] = useState(true)
useLoad(() => {
if (id) fetchDetail(id)
@@ -19,6 +20,9 @@ export default function Detail() {
setProduct(res)
} catch (err) {
console.error(err)
+ Taro.showToast({ title: '加载失败', icon: 'none' })
+ } finally {
+ setLoading(false)
}
}
@@ -29,50 +33,91 @@ export default function Detail() {
})
}
- if (!product) return Loading...
+ if (loading) return Loading...
+ if (!product) return Product Not Found
return (
-
-
-
- {product.name}
- ¥{product.price}
-
-
-
- 芯片
- {product.chip_type}
-
-
- Flash
- {product.flash_size}MB
-
-
- RAM
- {product.ram_size}MB
-
-
-
-
- 产品介绍
- {product.description}
+ {/* Hero Section */}
+
+
+ {product.detail_image_url || product.static_image_url ? (
+
+ ) : (
+
+ ⚡
+
+ )}
+
- {product.features && product.features.map((f, idx) => (
-
- • {f.title}
- {f.description}
-
- ))}
+
+ {product.name}
+ {product.description}
+
+
+ {product.chip_type}
+ {product.has_camera && 高清摄像头}
+ {product.has_microphone && 阵列麦克风}
+
+
+
+ {/* Stats Section */}
+
+
+ 售价
+ ¥{product.price}
+
+
+
+ 库存
+ {product.stock}件
+
+
+
+ {/* Features Section */}
+
+ {product.features && product.features.length > 0 ? (
+ product.features.map((f, idx) => (
+
+
+ {f.icon_url ? : ★}
+
+
+ {f.title}
+ {f.description}
+
+
+ ))
+ ) : (
+
+ 极致性能释放
+ {product.chip_type} 强劲核心,提供强大的边缘计算算力支持。
+
+ )}
+
+
+ {/* Detail Image */}
+ {product.detail_image_url && (
+
+
+
+ )}
+
+
-
-
-
-
+ {/* Bottom Bar */}
+
+
+ Taro.switchTab({ url: '/pages/cart/cart' })}>
+ 🛒
+ 购物车
+
+
+
diff --git a/miniprogram/src/pages/index/index.scss b/miniprogram/src/pages/index/index.scss
index 5affc1b..4bb7997 100644
--- a/miniprogram/src/pages/index/index.scss
+++ b/miniprogram/src/pages/index/index.scss
@@ -1,50 +1,79 @@
.page-container {
- min-height: 100vh;
+ height: 100vh;
background-color: #000;
color: #fff;
- padding: 20px;
+ overflow: hidden;
+ position: relative;
+}
+
+.content-scroll {
+ height: 100vh;
+ position: relative;
+ z-index: 1;
+ // Ensure no padding here
+}
+
+.scroll-inner {
+ // Container for scroll content
+ width: 100%;
}
.header {
text-align: center;
- margin-bottom: 40px;
- padding-top: 40px;
+ padding: 60px 20px 40px;
+ position: relative;
- .logo-placeholder {
- font-size: 24px;
- font-weight: bold;
- color: #00f0ff;
- margin-bottom: 20px;
- letter-spacing: 2px;
+ .logo-box {
+ margin-bottom: 30px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .logo-img {
+ width: 120px;
+ height: 120px;
+ margin-bottom: 15px;
+ filter: drop-shadow(0 0 15px rgba(0, 240, 255, 0.4));
+ }
+
+ .logo-text {
+ font-size: 40px;
+ font-weight: 900;
+ color: #fff;
+ letter-spacing: 6px;
+ text-shadow: 0 0 20px rgba(0, 240, 255, 0.6);
+ }
}
.title-container {
- margin-bottom: 20px;
+ margin-bottom: 25px;
display: flex;
justify-content: center;
align-items: center;
+ height: 60px;
}
.title-text {
- font-size: 32px;
+ font-size: 36px;
font-weight: bold;
color: #00f0ff;
- text-shadow: 0 0 10px rgba(0, 240, 255, 0.5);
+ text-shadow: 0 0 15px rgba(0, 240, 255, 0.5);
}
.cursor {
- font-size: 32px;
+ font-size: 36px;
color: #fff;
- margin-left: 5px;
+ margin-left: 8px;
animation: blink 1s infinite;
}
.subtitle {
color: #aaa;
- font-size: 14px;
+ font-size: 26px;
line-height: 1.6;
display: block;
- padding: 0 20px;
+ padding: 0 40px;
+ font-weight: 300;
}
}
@@ -53,108 +82,158 @@
50% { opacity: 0; }
}
-.product-scroll {
- width: 100%;
- white-space: nowrap;
+.status-box {
+ padding: 100px 0;
+ text-align: center;
+
+ .loading-text { color: #00f0ff; font-size: 28px; }
+ .error-text { color: #ff4d4f; font-size: 28px; margin-bottom: 20px; display: block;}
+ .btn-retry { background: rgba(255,255,255,0.1); color: #fff; font-size: 24px; padding: 10px 40px; display: inline-block;}
}
-.product-list {
+.product-grid {
+ padding: 0 30px;
display: flex;
- padding-bottom: 20px;
+ flex-direction: column;
+ gap: 40px;
}
.card {
- display: inline-block;
- width: 280px;
- background: linear-gradient(135deg, rgba(31,31,31,0.9), rgba(42,42,42,0.9));
- border-radius: 12px;
- margin-right: 20px;
+ background: rgba(255, 255, 255, 0.03);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border-radius: 24px;
overflow: hidden;
- border: 1px solid rgba(255,255,255,0.1);
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.98);
+ border-color: #00b96b;
+ box-shadow: 0 0 30px rgba(0, 185, 107, 0.2);
+ }
&-cover {
- height: 180px;
- background: #222;
+ height: 360px;
+ background: #111;
+ position: relative;
+ overflow: hidden;
.card-img {
width: 100%;
height: 100%;
+ transition: transform 0.5s ease;
+ }
+
+ .placeholder-img {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: radial-gradient(circle at center, #222, #111);
+ .icon-rocket { font-size: 100px; }
+ }
+
+ .card-overlay {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 50%;
+ background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
}
}
&-body {
- padding: 16px;
+ padding: 30px;
}
- &-title {
- font-size: 18px;
- font-weight: bold;
- color: #00f0ff;
- display: block;
- margin-bottom: 8px;
- white-space: normal;
+ &-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 15px;
+
+ .card-title {
+ font-size: 36px;
+ font-weight: bold;
+ color: #fff;
+ flex: 1;
+ margin-right: 20px;
+ line-height: 1.3;
+ text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+ }
+
+ .price {
+ font-size: 36px;
+ color: #00b96b;
+ font-weight: 900;
+ text-shadow: 0 0 15px rgba(0, 185, 107, 0.3);
+ }
}
&-desc {
- font-size: 12px;
- color: #bbb;
- display: block;
- margin-bottom: 12px;
- height: 36px;
- overflow: hidden;
- white-space: normal;
+ font-size: 26px;
+ color: #ccc;
+ line-height: 1.5;
+ margin-bottom: 25px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
+ overflow: hidden;
}
.tags {
display: flex;
- gap: 8px;
- margin-bottom: 12px;
flex-wrap: wrap;
+ gap: 12px;
+ margin-bottom: 30px;
.tag {
- font-size: 10px;
- padding: 2px 6px;
- border-radius: 4px;
+ padding: 8px 18px;
+ border-radius: 12px;
+ font-size: 22px;
+ font-weight: 500;
&.cyan {
- color: cyan;
- background: rgba(0,255,255,0.1);
- border: 1px solid cyan;
+ color: #00f0ff;
+ background: rgba(0, 240, 255, 0.1);
+ border: 1px solid rgba(0, 240, 255, 0.3);
}
-
&.blue {
- color: blue;
- background: rgba(0,0,255,0.1);
- border: 1px solid blue;
+ color: #3b82f6;
+ background: rgba(59, 130, 246, 0.1);
+ border: 1px solid rgba(59, 130, 246, 0.3);
+ }
+ &.purple {
+ color: #a855f7;
+ background: rgba(168, 85, 247, 0.1);
+ border: 1px solid rgba(168, 85, 247, 0.3);
}
}
}
&-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- .price {
- font-size: 20px;
- color: #00b96b;
+ .btn-buy {
+ background: linear-gradient(90deg, #00b96b, #00f0ff);
+ color: #000;
font-weight: bold;
- }
-
- .btn-arrow {
- width: 30px;
- height: 30px;
- border-radius: 50%;
- background: #00b96b;
- color: #fff;
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 16px;
+ font-size: 30px;
+ border-radius: 50px;
+ border: none;
+ height: 80px;
+ line-height: 80px;
+ box-shadow: 0 5px 20px rgba(0, 185, 107, 0.3);
+
+ &:active {
+ opacity: 0.9;
+ }
}
}
}
+
+.footer-spacer {
+ height: 100px;
+}
diff --git a/miniprogram/src/pages/index/index.tsx b/miniprogram/src/pages/index/index.tsx
index 60d3b67..d8c913a 100644
--- a/miniprogram/src/pages/index/index.tsx
+++ b/miniprogram/src/pages/index/index.tsx
@@ -2,6 +2,7 @@ import { View, Text, Image, ScrollView, Button } from '@tarojs/components'
import Taro, { useLoad } from '@tarojs/taro'
import { useState, useEffect } from 'react'
import { getConfigs } from '../../api'
+import ParticleBackground from '../../components/ParticleBackground'
import './index.scss'
export default function Index() {
@@ -30,12 +31,10 @@ export default function Index() {
setError('')
try {
const res: any = await getConfigs()
- console.log('Configs fetched:', res)
- // Adapt to different API response structures
const list = Array.isArray(res) ? res : (res.results || res.data || [])
setProducts(list)
} catch (err: any) {
- console.error('Fetch error:', err)
+ console.error(err)
setError(err.errMsg || '加载失败,请检查网络')
} finally {
setLoading(false)
@@ -48,34 +47,34 @@ export default function Index() {
return (
-
-
- QUANT SPEED
+
+
+
+
+
+
+
+ QUANT SPEED
+
+
+
+ {typedText}
+ |
+
+ 量迹 AI 硬件为您提供最强大的边缘计算能力
-
-
- {typedText}
- |
-
- 量迹 AI 硬件为您提供最强大的边缘计算能力
-
- {loading ? (
-
- 正在加载硬件配置...
-
- ) : error ? (
-
- {error}
-
-
- ) : products.length === 0 ? (
-
- 暂无硬件产品
-
- ) : (
-
-
+ {loading ? (
+
+ 正在加载硬件配置...
+
+ ) : error ? (
+
+ {error}
+
+
+ ) : (
+
{products.map((item) => (
goToDetail(item.id)}>
@@ -86,25 +85,35 @@ export default function Index() {
🚀
)}
+
+
- {item.name}
+
+ {item.name}
+ ¥{item.price}
+
+
{item.description}
+
{item.chip_type}
{item.has_camera && Camera}
{item.has_microphone && Mic}
+
- ¥{item.price}
- →
+
))}
-
- )}
+ )}
+
+
+
+
)
}
diff --git a/miniprogram/src/utils/request.ts b/miniprogram/src/utils/request.ts
index c2eab2e..42b119d 100644
--- a/miniprogram/src/utils/request.ts
+++ b/miniprogram/src/utils/request.ts
@@ -1,6 +1,6 @@
import Taro from '@tarojs/taro'
-const BASE_URL = process.env.TARO_APP_API_URL || 'http://localhost:8000/api'
+const BASE_URL = process.env.TARO_APP_API_URL || 'https://market.quant-speed.com/api'
export const request = async (options: Taro.request.Option) => {
const token = Taro.getStorageSync('token')