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')