From c3b4373c94fcbccd9e7e8160f224acf4e9eba687 Mon Sep 17 00:00:00 2001 From: jeremygan2021 Date: Wed, 11 Feb 2026 01:31:29 +0800 Subject: [PATCH] mini --- backend/check_urls.py | 30 ++ .../__pycache__/settings.cpython-312.pyc | Bin 4089 -> 5773 bytes .../__pycache__/settings.cpython-313.pyc | Bin 4089 -> 5506 bytes backend/config/settings.py | 118 ++++++- .../shop/__pycache__/admin.cpython-312.pyc | Bin 11191 -> 15039 bytes .../shop/__pycache__/admin.cpython-313.pyc | Bin 11475 -> 15427 bytes .../__pycache__/serializers.cpython-313.pyc | Bin 9384 -> 11162 bytes backend/shop/__pycache__/urls.cpython-313.pyc | Bin 1245 -> 1521 bytes .../shop/__pycache__/views.cpython-313.pyc | Bin 34245 -> 45764 bytes backend/shop/admin.py | 104 ++++++- miniprogram/src/assets/logo.svg | 6 + .../components/ParticleBackground/index.scss | 10 + .../components/ParticleBackground/index.tsx | 175 +++++++++++ miniprogram/src/pages/goods/detail.scss | 287 ++++++++++++------ miniprogram/src/pages/goods/detail.tsx | 115 ++++--- miniprogram/src/pages/index/index.scss | 233 +++++++++----- miniprogram/src/pages/index/index.tsx | 77 ++--- miniprogram/src/utils/request.ts | 2 +- 18 files changed, 894 insertions(+), 263 deletions(-) create mode 100644 backend/check_urls.py create mode 100644 miniprogram/src/assets/logo.svg create mode 100644 miniprogram/src/components/ParticleBackground/index.scss create mode 100644 miniprogram/src/components/ParticleBackground/index.tsx 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 340c2b461544bca772536967564eb8988dcb704d..67a96a24037e81416847d8c8151fb660661f1d08 100644 GIT binary patch delta 2229 zcmZvd+fx%)9LKXsxHK0)@K&vOz*-YUVUrMo+FGpI;$76X(o4GJEXit;&FlqA9=ckm z>Sz(2$dt}xrbFvYowkFt%(PP&^bcqsI+K@lpE^@EfwwT7KJ=}>JsV*&m1(qguCJQetf`cVVm*6?E_tt(_edfwR?B9v1xgi2^GJ+U$}j>IQhfE+|TWW?5K8oQX9XaWoHdr zm1#WP0c9Yb6 zsHrQ2x*>u~--l2qje?;}o3mRvCcB{O;)6$pTfY>iCX4rO5THGoT6j8XV6{5M@~JqR zMhs4H$^BxCPlbosNQ{wWkrl$QNJiN>CrVzMS|xE(oK@{ygiosWIG4O4*Fym9){UhH zSGD^SMhvx4lvAuQ#3>>#gqcW$my=Su#iTDXaaIHqkx!Ze$oiwxrpJnp?r5`PE23(3 zRN&>5SfPL+A%Po^C5T{7QJ_IXeL-YXjKC;xs#b9ln-Gn!;FyLZt`Q`TCefc2fHx)urQG^4R&$rYi)E~ zyY4c=^GlVgAqk8noO`Tx>l2CIhE%NjNL2V zoi1O3jZ8!#lMfFtaV80M4>R#ND^*@JPKsfHffI9ANNw}qEZw}nI5n-!kE`_wKFLa4 znvpoTF?=$;!<12kPb4@ip?#|v{428OrJjF3Wi znq-YP04Hmwz51vYryedozNXfSY(y3~X(YYHP+(*!7M2gMc|cBhUc+ILWrRpf-9R?b zlrWP@#kmNnPE;L9W{8WDjkhWjb?&KCYtHu`IeNIKZ%O|rl9_N0x=z7DwF^8id0S^3 zt~9mIi7&%T<@d=#TohV$yX)>xoH8tQC$Y~q0rL0>95*# z`No&?O*@}`VGT#{H5RS;rk3SRuJw+Rt**Yhge;q1%D22yszJPHtcHiyow(4g|ncolDI*wVOMv))}-YFiaOlt;VQy0_)g z9zsv%(5V$splyVn&Y?2~eXb@X+O818<2iI<#T~HWelCal%rr!7E2;upn$W%+I&avv zY|ztx5z-H?rXS2BFR{CjLl@1Ov{6-W4jnhJ+o`+PgZl~VMQW2{x#kkpP`$h>K{>2f Qt4jm6%#->qy9guv9}?9Oy#N3J delta 489 zcmeCx{VA_;nwOW00SF>px-)NyGB7*_abQ3I%J|&IxKYEJkx_ke9;2MW9PUiU6!SSO zQL-tlQF193DV8Z#Db^`AnT$Yjt8~T`+Z4MwOeyw274nr#nhu+PFja8Wmr4K)d%k?) zv#y4xt!tk4?|8Cn`P0db&(|${KB28t4y)vgEo-0b-0*z!?&rJb-(u9i#a>X9nVVQt zStU@Eo}^)9VxVATVyR$cYN`npF){&)7y(60pdyAAW(r2eKy~$|#xNlxQw1YKL!crf z%_@G7L8fL3h893`Oe~-Rh9(MzWXNdut001Z-klg?P diff --git a/backend/config/__pycache__/settings.cpython-313.pyc b/backend/config/__pycache__/settings.cpython-313.pyc index cb9f72fe793f9ce8cd67f5abf70d1e8e2c2c74a7..b372ee504060b543f432f0504a02b03d91168bbd 100644 GIT binary patch delta 1903 zcmZuw?N1wZ9H&o!Eo~W$p-kilbB+q3z4BBh6JfB4n@{}QbUvL&IofmWUA+egeN(nX zMmAX?g^0-#gD#O6!%z~95Vrro*Oz-`BG=M)C41#7{oY-HL%jT&ejh*I&-eHJ{{C*b z-O~Kj?RJ*H_Xl&2S!yaN`AbvwzY|5_X~6n(GigaVa5oNN2kyb=@hiBu&4w@FS8<;% zy@vbofF}8E_#$`)A#Mm?!k2aL>v$Nyp-WfrRs5zY?k$LW8^43!)x9GaP{bW)#YY+Z=p7cJMFYF(6IjaU2O) z0v1gBEjWsqHY@(T&CWMOtiGEYzgVWqQy%Cwe~ zhz>*$5Yz!>^n@URdNfXK9eUFQ~8v$MbD z)>d-!GrB>2yteaX#V{+S37Sj9=p>@X;tYFUh;j*Xk`6~HQ4(mL1c8mvF-8!5HpL+_ zVvJVoOqgR8dyHXkN?s_S&d%f)r`3gJqlQu?NC}#sU{V6dlT#H8G%kvJe2~k;$TEN zE*O2oj5D{27jas<8GQrtU(V&W*HAN*&VIA0rf+AL)_qEOl;X!GDV{!37ihN!q5E+r zVyY{Z16cpCu}M%J7DpsSE;W^`H+QYwNtiyCH?p&x$^ZD3;+$aUNs?hnicdC}@+ck# z@!w2susfR%v&$LNV0YKPQ|Fe{*{^q3Ru#99f>|aQ7MKE`Hvjq+&5zJi2I%7#-s&@R)B! z?``}0{GEl}wRLrSN%6)xmKK>LB{Hxv9Gg62Vie}$aR$h8Y>YcHl!}x#;*G7`*0;up zjRn(}KfCy7XKUFo!&|4O^hVE`V2w8bp5xO#d{nz@4|gBkQpyE7Eb)vum8>%mC`pWx z(!j9?`R3Z^$!g_at;$*1_CZa1oM<(KG@hD|@cyMT-ee_U3pO8{N72@cyvg-5ic&dAlha%J_O($ zw&J9-VDorO3#jDT=jFzi3Z*4=&2oJs{5J(;)GVWcy)t{vsX|3bQ@e~hWOVUB@6!E6 zy|%KhYOn01+mkA^9zyiV=w)+CtBhJ?)OP>@I-Gt(KY%bJ013a0E*xZl4jF@HJ!B>s zAZ0|zXvhd?tkGCK=k^r34oLxTpNuY>A%JABp}SpLh8HrfSg%=XoqJ_BES05u=i(Np Sb-J`LYRf$KcH6&z3;!1xv9_K7 delta 435 zcmZqD{wc5VnU|M~0SF>px-)NyGB7*_abQ3I%J|&IxKSgXQQKUPrARiIwMZ`5BG@w6 zD%d*M#(>d)G1y9|D+Pdax|BffS zmOq``_9=-GBHpvGO<)J zGBwqNiWr#yMT~$VCQuPW3o`{HW1u=yW0;7Mse+NAAyC9fvx*;Nj;Wb~p#{(w6AP$- zp^1W_8BmomP!C^`C&(%VLt`Mev;+wN`Nj%{2BttohMHBpAilW*&;Ub-dLY-(NWmOv zhM|FG6%R<-Lczpb!Q6QARc=crO_s?g`L)?zG6FsBHknBvjnRB_rT`NYcN|avBv|~6 zadVka2_uW{4PoQS?}WvbRc;6?gOL)LRQxE;Bf|B8nUA0CBd0JIQv=%vy~*yP<}5Kl F-2kvIb!-3t 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 092085d2667817f5cc12aeeff0fdcd7f4b667932..d98e11c9316bb8bb443e2a37ee45c127a5520d6d 100644 GIT binary patch delta 6424 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 delta 2899 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 diff --git a/backend/shop/__pycache__/views.cpython-313.pyc b/backend/shop/__pycache__/views.cpython-313.pyc index add1d4f849bf816fae4d8c27ec7a1f5ef0e02293..8cd5bcac4a7719d2920cc743edded45cc6066da8 100644 GIT binary patch delta 14351 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 S)Am*cbQT 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 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')