From 96d5598fb529b53bacdd0c3f85cf7f9d4b132d63 Mon Sep 17 00:00:00 2001 From: jeremygan2021 Date: Wed, 11 Feb 2026 03:00:38 +0800 Subject: [PATCH] vb --- backend/check_urls.py | 2 +- .../__pycache__/settings.cpython-312.pyc | Bin 5773 -> 5768 bytes backend/config/settings.py | 6 +- backend/db.sqlite3 | Bin 258048 -> 262144 bytes .../shop/__pycache__/admin.cpython-312.pyc | Bin 15039 -> 15328 bytes .../shop/__pycache__/models.cpython-312.pyc | Bin 23122 -> 24075 bytes .../__pycache__/serializers.cpython-312.pyc | Bin 10603 -> 11028 bytes backend/shop/__pycache__/urls.cpython-312.pyc | Bin 1563 -> 1566 bytes .../shop/__pycache__/views.cpython-312.pyc | Bin 45699 -> 45696 bytes backend/shop/admin.py | 28 +- .../0018_vbcourse_delete_arservice.py | 35 ++ ...mage_vbcourse_detail_image_url_and_more.py | 28 ++ .../0020_alter_vbcourse_course_type.py | 18 + backend/shop/models.py | 33 +- backend/shop/serializers.py | 17 +- backend/shop/urls.py | 4 +- backend/shop/views.py | 16 +- frontend/src/App.jsx | 4 +- frontend/src/api.js | 2 +- frontend/src/components/Layout.jsx | 4 +- .../pages/{ARExperience.jsx => VBCourses.jsx} | 62 +-- miniprogram/config/index.js | 2 +- miniprogram/src/api/index.ts | 9 +- miniprogram/src/app.config.ts | 31 +- miniprogram/src/app.scss | 12 +- miniprogram/src/assets/AI_service.png | Bin 0 -> 3489 bytes miniprogram/src/assets/AI_service_active.png | Bin 0 -> 4470 bytes miniprogram/src/assets/VR.png | Bin 0 -> 4202 bytes miniprogram/src/assets/VR_active.png | Bin 0 -> 3744 bytes miniprogram/src/assets/cart.png | Bin 140 -> 3745 bytes miniprogram/src/assets/cart_active.png | Bin 79 -> 3881 bytes miniprogram/src/assets/home.png | Bin 140 -> 2582 bytes miniprogram/src/assets/home_active.png | Bin 79 -> 10496 bytes miniprogram/src/assets/logo.svg | 108 +++++- miniprogram/src/assets/user.png | Bin 140 -> 4204 bytes miniprogram/src/assets/user_active.png | Bin 79 -> 3533 bytes miniprogram/src/pages/ar/detail.tsx | 51 --- miniprogram/src/pages/cart/cart.scss | 218 ++++++++++- miniprogram/src/pages/cart/cart.tsx | 141 ++++++- .../pages/{ar => courses}/detail.config.ts | 0 .../src/pages/{ar => courses}/detail.scss | 24 +- miniprogram/src/pages/courses/detail.tsx | 70 ++++ .../src/pages/{ar => courses}/index.config.ts | 0 .../src/pages/{ar => courses}/index.scss | 37 ++ .../src/pages/{ar => courses}/index.tsx | 30 +- miniprogram/src/pages/goods/detail.scss | 356 ++++++++++++------ miniprogram/src/pages/goods/detail.tsx | 115 ++++-- miniprogram/src/pages/index/index.scss | 309 +++++++++++---- miniprogram/src/pages/index/index.tsx | 17 +- miniprogram/src/pages/order/checkout.scss | 213 ++++++++--- miniprogram/src/pages/order/checkout.tsx | 230 ++++++++--- miniprogram/src/pages/services/index.scss | 151 ++++++-- miniprogram/src/pages/services/index.tsx | 6 +- miniprogram/src/pages/user/index.scss | 228 +++++++++-- miniprogram/src/pages/user/index.tsx | 104 ++++- .../src/subpackages/distributor/index.tsx | 5 +- miniprogram/src/utils/cart.ts | 90 +++++ 57 files changed, 2239 insertions(+), 577 deletions(-) create mode 100644 backend/shop/migrations/0018_vbcourse_delete_arservice.py create mode 100644 backend/shop/migrations/0019_vbcourse_detail_image_vbcourse_detail_image_url_and_more.py create mode 100644 backend/shop/migrations/0020_alter_vbcourse_course_type.py rename frontend/src/pages/{ARExperience.jsx => VBCourses.jsx} (57%) create mode 100644 miniprogram/src/assets/AI_service.png create mode 100644 miniprogram/src/assets/AI_service_active.png create mode 100644 miniprogram/src/assets/VR.png create mode 100644 miniprogram/src/assets/VR_active.png delete mode 100644 miniprogram/src/pages/ar/detail.tsx rename miniprogram/src/pages/{ar => courses}/detail.config.ts (100%) rename miniprogram/src/pages/{ar => courses}/detail.scss (67%) create mode 100644 miniprogram/src/pages/courses/detail.tsx rename miniprogram/src/pages/{ar => courses}/index.config.ts (100%) rename miniprogram/src/pages/{ar => courses}/index.scss (71%) rename miniprogram/src/pages/{ar => courses}/index.tsx (66%) create mode 100644 miniprogram/src/utils/cart.ts diff --git a/backend/check_urls.py b/backend/check_urls.py index 63e303d..6dd3a96 100644 --- a/backend/check_urls.py +++ b/backend/check_urls.py @@ -12,7 +12,7 @@ links = [ "admin:shop_distributor_changelist", "admin:shop_esp32config_changelist", "admin:shop_service_changelist", - "admin:shop_arservice_changelist", + "admin:shop_vbcourse_changelist", "admin:shop_order_changelist", "admin:shop_serviceorder_changelist", "admin:shop_withdrawal_changelist", diff --git a/backend/config/__pycache__/settings.cpython-312.pyc b/backend/config/__pycache__/settings.cpython-312.pyc index 67a96a24037e81416847d8c8151fb660661f1d08..c8aed22fd73879393f2c5fdd077a2c70726b1001 100644 GIT binary patch delta 83 zcmeCx?a<{t&CAQh00eb)-I;M4dHwlR!kk{L-}ih)_bs;KW7dHwk`9fO|io&0iD&n>R9%+&Ju%)I!-qAK~sl-$fb ntKy9O0wA|IwWusJd2<8b7d9@Pn+iG?bp0+X_;0QgNeVuzJk?J5?U;HW1GE(0rya>+=PCDIq6`#Z>9!RAHewFQ#@j>8)8Y;0p|Rd;yQ& z$NMAEa3mV?_XmTK@IY8_83|_U37hsDlS?m^!S?7Gxi>7IuRzvCHfy?2l|08(=5dIJ2d;%-&|?-}B{E1up}8xiunUf^FH{`K_Gcm0KHKAXd2AX%AGWj zfmsEc(086X2YARa6$M=dE|RHWPZNy8&QXN3bMIqot9L+g?VyGDlQ4F{HTF33h0SEX zMfTD!f)$fP>!^@6nxYqpPTfG8@rBootx1p4m4Thgi9CFg!eh0JO)hG`yaB%i+O7A= z!$0)c03yr^eS&UBKU4RSj3%fUyaIV}5g^-^ zy-*aSY$2~)d6#UnV#fJGUjIn)k}@emr#YLSmx>EhQlY3L+Lew|Kv1F|kZ~vp(>lcg W(7V1#jce3Z_>WiRE1PWX9RD8$JcY6V delta 553 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 QgO@w6>{71Fs430mFECTcSO5S3 diff --git a/backend/shop/__pycache__/serializers.cpython-312.pyc b/backend/shop/__pycache__/serializers.cpython-312.pyc index ae896a2548fdedd5d639f27ce699076103912377..8cf1e44ba2ec2af971957430dcf654a8c40145db 100644 GIT binary patch delta 711 zcmaDIG$oAhG%qg~0}%YJ>&`5d*~lly#K95f96SIGu+XP0+SEQ2~X}7 z;;ZLmVsK|jVQpbZVOz}v(#OCM#g)R|!Vtxs!T}_CDmgW|ZV5q6NDWRc%1q43tV%5^ zl>;i}QUHQ5rx)w@JzvrNbXWh=j_FT(rahgx0xWS$JUz7}J{e?nYJ5p$L27(TW^qAI zVr58?22gV{5Kk79(d86}nLgQHMt!oJq_R*qP^tiki+O-V3&RZoiOIISa+6QWbWQ#( z8Ni_h)i8NKzr^GmDQ?D+$rVy|lSKrkPY#xq;m}5wmKEgTtOlA=1hy_+P<`?ZDN)r_ zkVp%VxFw9meMQP3>Hf+0WF;oEN%Mt=0l6&+8}s7o}}3OWR#wk+>nH4^p9jQA+=UdBBC3gey{sKy}|)m{_GhG60EB9IUJ& zpZPZ1Nk=oXP6ry^xp;^cG;3QjF5%S_&E!t_^kaw11Cv#?{(H->(2gJoZK%#}=hJfVc{gOYJwSoHn3CK-eEyc}P zH2JZV-DF8_p~(s|s%*+Ybwz!X^JNqnH78G$k>&Z$!o({5kpW12=GgpPCYq6T3Q$SI zVs|-4#+Jz*^5I+)L0UjW<>Zy}&WsZ`zm-49#62G*Y7HXhO+Kh3#<*be4J9AenLvT* zn>Cd?7#U|x-lUSrxM;JCDjyTuERe#vlMU2EV9YLc2gcQtPpd!inGKTj2N82X#6l3U z2t<^D2(W`!fLM(nq7_860f}NpAmLD?1maJJ5j~TCYs_MtK6#pEmnYcSU>OCFv?7QA zslLTwlbfGXnv-f*v=+z(8B@F;NPJ*sWMsU{p!S`Kfzg}kJ5UBnPR`US65?kRnW24M T%Jia?=@$kd=Zn(hw_54|^(%R~ diff --git a/backend/shop/__pycache__/urls.cpython-312.pyc b/backend/shop/__pycache__/urls.cpython-312.pyc index 2d8534faaa9ffc438b776d76f6051f6ab2d0af50..f69011531990f0b3666f9e9f326caa7f385ac936 100644 GIT binary patch delta 66 zcmbQuGmnS&G%qg~0}vF{c4zu-QyFz8I~V4VyAj0Y6# delta 63 zcmbQoGn(2Dr$lJ@rDc~3soLW?tnLK$B(?n*b#G=jC%r6)jKTlR+ SO=M@iAg+FeL1S_^>tp~%2@^{I diff --git a/backend/shop/__pycache__/views.cpython-312.pyc b/backend/shop/__pycache__/views.cpython-312.pyc index 0d50a17dbbc5afe2645194437801f7fdb954e713..db1c7c7a9be79daf37148d399e6e12f2748f4f24 100644 GIT binary patch delta 289 zcmZpE%GB_biT5-wFBbz4oU7^1e6x`^ij9LK%*i>ww5WJ;0b3u35R|i-oxLy&DDz_d zzUM2tCpW|@xWPD@OhvUo1B!})M5zdnc+t20Y5z2s^o#Y&o;A0YD#In7^|U`-Hwz{` z`C04|C4Q(asbQI^<-w^XrHVk+Tnaz{SGG28gM!3$e))_1^4Iy5FY+s2=2yMWuXmAO zZ$<58e%l6*8{(RqYvb$vRil``GcYj1$d3><69c0YGf3Qt`7?;zthoL$qlDIkjO${? b7sZS}Gq5shedc6lV*0=VWNnt&n9c|Q9#?Ff delta 265 zcmZp8%GCUniT5-wFBbz4XvKGDe%#0##m33$7!;gZRF;`MIiIbMQy9kE%*tLE#^D(B zY)#ugW@>qGYDuXgP%)PR5WtL@yee*kg5-66g^T*6LC#Z5jl Wurg|Y=457K`oIBXZI;}a&Ika`3|Nu? diff --git a/backend/shop/admin.py b/backend/shop/admin.py index fc08102..81f2527 100644 --- a/backend/shop/admin.py +++ b/backend/shop/admin.py @@ -4,7 +4,7 @@ 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, WeChatUser, Distributor, Withdrawal, ServiceOrder +from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, VBCourse, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder import qrcode from io import BytesIO import base64 @@ -19,11 +19,11 @@ class ExternalUploadWidget(forms.URLInput): super().__init__(*args, **kwargs) self.upload_url = upload_url self.attrs.update({ - 'class': 'upload-url-input', + 'class': 'upload-url-input vTextField', 'data-upload-url': upload_url, 'data-accept': accept, - 'readonly': 'readonly', - 'placeholder': '上传文件后自动生成URL' + 'placeholder': '上传文件后自动生成URL', + 'style': 'width: 100%;' }) class Media: @@ -141,18 +141,26 @@ class ServiceOrderAdmin(ModelAdmin): }), ) -@admin.register(ARService) -class ARServiceAdmin(ModelAdmin): - list_display = ('title', 'created_at') - search_fields = ('title', 'description') +@admin.register(VBCourse) +class VBCourseAdmin(ModelAdmin): + list_display = ('title', 'course_type', 'tag', 'instructor', 'lesson_count', 'duration', 'created_at') + search_fields = ('title', 'description', 'instructor', 'tag') + list_filter = ('course_type', 'instructor', 'tag') fieldsets = ( ('基本信息', { - 'fields': ('title', 'description') + 'fields': ('title', 'description', 'course_type', 'tag') }), - ('封面/长图', { + ('课程详情', { + 'fields': ('instructor', 'duration', 'lesson_count') + }), + ('封面', { 'fields': ('cover_image', 'cover_image_url'), 'description': '图片上传和URL二选一,优先使用URL' }), + ('详情页长图', { + 'fields': ('detail_image', 'detail_image_url'), + 'description': '图片上传和URL二选一,优先使用URL' + }), ) @admin.register(Salesperson) diff --git a/backend/shop/migrations/0018_vbcourse_delete_arservice.py b/backend/shop/migrations/0018_vbcourse_delete_arservice.py new file mode 100644 index 0000000..4975f0f --- /dev/null +++ b/backend/shop/migrations/0018_vbcourse_delete_arservice.py @@ -0,0 +1,35 @@ +# Generated by Django 6.0.1 on 2026-02-10 18:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0017_withdrawal'), + ] + + operations = [ + migrations.CreateModel( + name='VBCourse', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=100, verbose_name='课程名称')), + ('description', models.TextField(verbose_name='课程简介')), + ('course_type', models.CharField(choices=[('software', '软件课程'), ('hardware', '硬件课程')], default='software', max_length=20, verbose_name='课程类型')), + ('duration', models.CharField(default='30分钟', help_text='例如: 30分钟', max_length=50, verbose_name='课程时长')), + ('lesson_count', models.IntegerField(default=1, verbose_name='课时数量')), + ('instructor', models.CharField(default='VB讲师', max_length=50, verbose_name='讲师')), + ('cover_image', models.ImageField(blank=True, null=True, upload_to='courses/covers/', verbose_name='封面图 (上传)')), + ('cover_image_url', models.URLField(blank=True, null=True, verbose_name='封面图 (URL)')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ], + options={ + 'verbose_name': 'VB课程', + 'verbose_name_plural': 'VB课程管理', + }, + ), + migrations.DeleteModel( + name='ARService', + ), + ] diff --git a/backend/shop/migrations/0019_vbcourse_detail_image_vbcourse_detail_image_url_and_more.py b/backend/shop/migrations/0019_vbcourse_detail_image_vbcourse_detail_image_url_and_more.py new file mode 100644 index 0000000..8a4e192 --- /dev/null +++ b/backend/shop/migrations/0019_vbcourse_detail_image_vbcourse_detail_image_url_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 6.0.1 on 2026-02-10 18:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0018_vbcourse_delete_arservice'), + ] + + operations = [ + migrations.AddField( + model_name='vbcourse', + name='detail_image', + field=models.ImageField(blank=True, null=True, upload_to='courses/details/', verbose_name='详情页长图 (上传)'), + ), + migrations.AddField( + model_name='vbcourse', + name='detail_image_url', + field=models.URLField(blank=True, help_text='如果填写了URL,将优先使用URL', null=True, verbose_name='详情页长图 (URL)'), + ), + migrations.AddField( + model_name='vbcourse', + name='tag', + field=models.CharField(blank=True, help_text='例如: 热门, 推荐, 进阶', max_length=20, verbose_name='标签'), + ), + ] diff --git a/backend/shop/migrations/0020_alter_vbcourse_course_type.py b/backend/shop/migrations/0020_alter_vbcourse_course_type.py new file mode 100644 index 0000000..857e060 --- /dev/null +++ b/backend/shop/migrations/0020_alter_vbcourse_course_type.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.1 on 2026-02-10 18:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0019_vbcourse_detail_image_vbcourse_detail_image_url_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='vbcourse', + name='course_type', + field=models.CharField(choices=[('software', '软件课程'), ('hardware', '硬件课程'), ('incubation', '产品商业孵化')], default='software', max_length=20, verbose_name='课程类型'), + ), + ] diff --git a/backend/shop/models.py b/backend/shop/models.py index b1c49b4..2623222 100644 --- a/backend/shop/models.py +++ b/backend/shop/models.py @@ -312,19 +312,36 @@ class ServiceOrder(models.Model): verbose_name_plural = "服务订单列表" -class ARService(models.Model): +class VBCourse(models.Model): """ - AR体验服务模型 + VB Coding 课程模型 """ - title = models.CharField(max_length=100, verbose_name="体验名称") - description = models.TextField(verbose_name="简介") - cover_image = models.ImageField(upload_to='ar/covers/', blank=True, null=True, verbose_name="封面/长图 (上传)") - cover_image_url = models.URLField(blank=True, null=True, verbose_name="封面/长图 (URL)") + COURSE_TYPE_CHOICES = ( + ('software', '软件课程'), + ('hardware', '硬件课程'), + ('incubation', '产品商业孵化'), + ) + + title = models.CharField(max_length=100, verbose_name="课程名称") + description = models.TextField(verbose_name="课程简介") + course_type = models.CharField(max_length=20, choices=COURSE_TYPE_CHOICES, default='software', verbose_name="课程类型") + duration = models.CharField(max_length=50, verbose_name="课程时长", help_text="例如: 30分钟", default="30分钟") + lesson_count = models.IntegerField(default=1, verbose_name="课时数量") + instructor = models.CharField(max_length=50, verbose_name="讲师", default="VB讲师") + + tag = models.CharField(max_length=20, blank=True, verbose_name="标签", help_text="例如: 热门, 推荐, 进阶") + + cover_image = models.ImageField(upload_to='courses/covers/', blank=True, null=True, verbose_name="封面图 (上传)") + cover_image_url = models.URLField(blank=True, null=True, verbose_name="封面图 (URL)") + + detail_image = models.ImageField(upload_to='courses/details/', blank=True, null=True, verbose_name="详情页长图 (上传)") + detail_image_url = models.URLField(blank=True, null=True, verbose_name="详情页长图 (URL)", help_text="如果填写了URL,将优先使用URL") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") def __str__(self): return self.title class Meta: - verbose_name = "AR体验" - verbose_name_plural = "AR体验管理" + verbose_name = "VB课程" + verbose_name_plural = "VB课程管理" diff --git a/backend/shop/serializers.py b/backend/shop/serializers.py index 6e1fdd1..8c4d7e8 100644 --- a/backend/shop/serializers.py +++ b/backend/shop/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import ESP32Config, Order, Salesperson, Service, ARService, ProductFeature, ServiceOrder, WeChatUser, Distributor, Withdrawal +from .models import ESP32Config, Order, Salesperson, Service, VBCourse, ProductFeature, ServiceOrder, WeChatUser, Distributor, Withdrawal class WeChatUserSerializer(serializers.ModelSerializer): class Meta: @@ -101,14 +101,16 @@ class ServiceOrderSerializer(serializers.ModelSerializer): return super().create(validated_data) -class ARServiceSerializer(serializers.ModelSerializer): +class VBCourseSerializer(serializers.ModelSerializer): """ - AR服务序列化器 + VB课程序列化器 """ display_cover_image = serializers.SerializerMethodField() + display_detail_image = serializers.SerializerMethodField() + course_type_display = serializers.CharField(source='get_course_type_display', read_only=True) class Meta: - model = ARService + model = VBCourse fields = '__all__' def get_display_cover_image(self, obj): @@ -118,6 +120,13 @@ class ARServiceSerializer(serializers.ModelSerializer): return obj.cover_image.url return None + def get_display_detail_image(self, obj): + if obj.detail_image_url: + return obj.detail_image_url + if obj.detail_image: + return obj.detail_image.url + return None + class ESP32ConfigSerializer(serializers.ModelSerializer): """ ESP32配置序列化器 diff --git a/backend/shop/urls.py b/backend/shop/urls.py index 209ab54..3464a6d 100644 --- a/backend/shop/urls.py +++ b/backend/shop/urls.py @@ -2,7 +2,7 @@ from django.urls import path, include, re_path from rest_framework.routers import DefaultRouter from .views import ( ESP32ConfigViewSet, OrderViewSet, order_check_view, - ServiceViewSet, ARServiceViewSet, ServiceOrderViewSet, + ServiceViewSet, VBCourseViewSet, ServiceOrderViewSet, payment_finish, pay, send_sms_code, wechat_login, update_user_info, DistributorViewSet ) @@ -10,7 +10,7 @@ router = DefaultRouter() router.register(r'configs', ESP32ConfigViewSet) router.register(r'orders', OrderViewSet) router.register(r'services', ServiceViewSet) -router.register(r'ar', ARServiceViewSet) +router.register(r'courses', VBCourseViewSet) router.register(r'service-orders', ServiceOrderViewSet) router.register(r'distributor', DistributorViewSet, basename='distributor') diff --git a/backend/shop/views.py b/backend/shop/views.py index 2cb693e..3982f2e 100644 --- a/backend/shop/views.py +++ b/backend/shop/views.py @@ -5,8 +5,8 @@ from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample -from .models import ESP32Config, Order, WeChatPayConfig, Service, ARService, ServiceOrder, Salesperson, CommissionLog, WeChatUser, Distributor, Withdrawal -from .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, ARServiceSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer +from .models import ESP32Config, Order, WeChatPayConfig, Service, VBCourse, ServiceOrder, Salesperson, CommissionLog, WeChatUser, Distributor, Withdrawal +from .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, VBCourseSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer from django.core.signing import TimestampSigner, BadSignature, SignatureExpired from django.contrib.auth.models import User from wechatpayv3 import WeChatPay, WeChatPayType @@ -508,15 +508,15 @@ def payment_finish(request): return HttpResponse(str(e), status=500) @extend_schema_view( - list=extend_schema(summary="获取AR服务列表", description="获取所有可用的AR服务"), - retrieve=extend_schema(summary="获取AR服务详情", description="获取指定AR服务的详细信息") + list=extend_schema(summary="获取VB课程列表", description="获取所有可用的VB课程"), + retrieve=extend_schema(summary="获取VB课程详情", description="获取指定VB课程的详细信息") ) -class ARServiceViewSet(viewsets.ReadOnlyModelViewSet): +class VBCourseViewSet(viewsets.ReadOnlyModelViewSet): """ - AR服务列表和详情 + VB课程列表和详情 """ - queryset = ARService.objects.all().order_by('-created_at') - serializer_class = ARServiceSerializer + queryset = VBCourse.objects.all().order_by('-created_at') + serializer_class = VBCourseSerializer def order_check_view(request): """ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 50bef69..f44e2cf 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,7 +6,7 @@ import ProductDetail from './pages/ProductDetail'; import Payment from './pages/Payment'; import AIServices from './pages/AIServices'; import ServiceDetail from './pages/ServiceDetail'; -import ARExperience from './pages/ARExperience'; +import VBCourses from './pages/VBCourses'; import MyOrders from './pages/MyOrders'; import 'antd/dist/reset.css'; import './App.css'; @@ -19,7 +19,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/frontend/src/api.js b/frontend/src/api.js index 48225bf..f01ac76 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -19,7 +19,7 @@ export const confirmPayment = (orderId) => api.post(`/orders/${orderId}/confirm_ export const getServices = () => api.get('/services/'); export const getServiceDetail = (id) => api.get(`/services/${id}/`); export const createServiceOrder = (data) => api.post('/service-orders/', data); -export const getARServices = () => api.get('/ar/'); +export const getVBCourses = () => api.get('/courses/'); export const sendSms = (data) => api.post('/auth/send-sms/', data); export const queryMyOrders = (data) => api.post('/orders/my_orders/', data); diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index ad6242d..6adaa09 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -34,9 +34,9 @@ const Layout = ({ children }) => { label: 'AI 服务', }, { - key: '/ar', + key: '/courses', icon: , - label: 'AR 体验', + label: 'VB 课程', }, { key: '/my-orders', diff --git a/frontend/src/pages/ARExperience.jsx b/frontend/src/pages/VBCourses.jsx similarity index 57% rename from frontend/src/pages/ARExperience.jsx rename to frontend/src/pages/VBCourses.jsx index 6dcc837..910c81f 100644 --- a/frontend/src/pages/ARExperience.jsx +++ b/frontend/src/pages/VBCourses.jsx @@ -1,28 +1,27 @@ import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; -import { Button, Typography, Spin, Row, Col, Empty } from 'antd'; -import { ScanOutlined } from '@ant-design/icons'; -import { getARServices } from '../api'; +import { Button, Typography, Spin, Row, Col, Empty, Tag } from 'antd'; +import { ReadOutlined, ClockCircleOutlined, UserOutlined, BookOutlined } from '@ant-design/icons'; +import { getVBCourses } from '../api'; const { Title, Paragraph } = Typography; -const ARExperience = () => { - const [scanning, setScanning] = useState(true); - const [arServices, setArServices] = useState([]); +const VBCourses = () => { + const [courses, setCourses] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { - const fetchAR = async () => { + const fetchCourses = async () => { try { - const res = await getARServices(); - setArServices(res.data); + const res = await getVBCourses(); + setCourses(res.data); } catch (error) { - console.error("Failed to fetch AR services:", error); + console.error("Failed to fetch VB Courses:", error); } finally { setLoading(false); } } - fetchAR(); + fetchCourses(); }, []); if (loading) return
; @@ -31,20 +30,20 @@ const ARExperience = () => {
- AR <span style={{ color: '#00f0ff' }}>UNIVERSE</span> + VB <span style={{ color: '#00f0ff' }}>CODING COURSES</span> - 探索全息增强现实体验。请佩戴您的设备,或使用移动端摄像头扫描空间。 + 探索 Vibe Coding 软件与硬件课程,开启您的编程之旅。
- {arServices.length === 0 ? ( + {courses.length === 0 ? (
- 暂无 AR 体验内容} /> + 暂无课程内容} />
) : ( - {arServices.map((item, index) => ( + {courses.map((item, index) => ( { border: '1px solid rgba(0,240,255,0.2)', borderRadius: 12, overflow: 'hidden', - height: '100%' + height: '100%', + display: 'flex', + flexDirection: 'column' }}> -
+
{item.display_cover_image ? ( {item.title} ) : ( - + )} +
+ {item.tag && ( + {item.tag} + )} + + {item.course_type_display || (item.course_type === 'hardware' ? '硬件课程' : '软件课程')} + +
-
-

{item.title}

-

{item.description}

+
+

{item.title}

+
+ {item.instructor} + {item.duration} + {item.lesson_count} 课时 +
+

{item.description}

@@ -108,4 +122,4 @@ const ARExperience = () => { ); }; -export default ARExperience; +export default VBCourses; diff --git a/miniprogram/config/index.js b/miniprogram/config/index.js index 77000c6..2d4ff5e 100644 --- a/miniprogram/config/index.js +++ b/miniprogram/config/index.js @@ -22,7 +22,7 @@ const config = { framework: 'react', compiler: 'webpack5', cache: { - enable: false // Disable cache to avoid potential issues + enable: true // Enable cache for better build performance }, mini: { postcss: { diff --git a/miniprogram/src/api/index.ts b/miniprogram/src/api/index.ts index 433f2d9..6bdc308 100644 --- a/miniprogram/src/api/index.ts +++ b/miniprogram/src/api/index.ts @@ -15,15 +15,18 @@ export const getServices = () => request({ url: '/services/' }) export const getServiceDetail = (id: number) => request({ url: `/services/${id}/` }) export const createServiceOrder = (data: any) => request({ url: '/service-orders/', method: 'POST', data }) -// AR Services -export const getARServices = () => request({ url: '/ar/' }) -export const getARServiceDetail = (id: number) => request({ url: `/ar/${id}/` }) +// VB Courses +export const getVBCourses = () => request({ url: '/courses/' }) +export const getVBCourseDetail = (id: number) => request({ url: `/courses/${id}/` }) // Distributor export const distributorRegister = (data: any) => request({ url: '/distributor/register/', method: 'POST', data }) export const distributorInfo = () => request({ url: '/distributor/info/' }) export const distributorInvite = () => request({ url: '/distributor/invite/', method: 'POST' }) export const distributorWithdraw = (amount: number) => request({ url: '/distributor/withdraw/', method: 'POST', data: { amount } }) +// TODO: Verify if these exist in the API docs +// export const distributorTeam = () => request({ url: '/distributor/team/' }) +// export const distributorHistory = () => request({ url: '/distributor/history/' }) // User export const updateUserInfo = (data: any) => request({ url: '/wechat/update/', method: 'POST', data }) diff --git a/miniprogram/src/app.config.ts b/miniprogram/src/app.config.ts index 41e0fa3..187cb73 100644 --- a/miniprogram/src/app.config.ts +++ b/miniprogram/src/app.config.ts @@ -3,8 +3,8 @@ export default defineAppConfig({ 'pages/index/index', 'pages/services/index', 'pages/services/detail', - 'pages/ar/index', - 'pages/ar/detail', + 'pages/courses/index', + 'pages/courses/detail', 'pages/goods/detail', 'pages/cart/cart', 'pages/order/checkout', @@ -25,14 +25,15 @@ export default defineAppConfig({ ], window: { backgroundTextStyle: 'light', - navigationBarBackgroundColor: '#fff', + navigationBarBackgroundColor: '#000000', navigationBarTitleText: 'Quant Speed Market', - navigationBarTextStyle: 'black' + navigationBarTextStyle: 'white' }, tabBar: { - color: "#999", - selectedColor: "#333", - backgroundColor: "#fff", + color: "#666666", + selectedColor: "#00b96b", + backgroundColor: "#000000", + borderStyle: "black", list: [ { pagePath: "pages/index/index", @@ -43,13 +44,19 @@ export default defineAppConfig({ { pagePath: "pages/services/index", text: "AI服务", - iconPath: "./assets/cart.png", // Using cart icon as placeholder if no other icon available - selectedIconPath: "./assets/cart_active.png" + iconPath: "./assets/AI_service.png", + selectedIconPath: "./assets/AI_service_active.png" }, { - pagePath: "pages/ar/index", - text: "AR体验", - iconPath: "./assets/cart.png", // Placeholder + pagePath: "pages/courses/index", + text: "VB课程", + iconPath: "./assets/VR.png", + selectedIconPath: "./assets/VR_active.png" + }, + { + pagePath: "pages/cart/cart", + text: "购物车", + iconPath: "./assets/cart.png", selectedIconPath: "./assets/cart_active.png" }, { diff --git a/miniprogram/src/app.scss b/miniprogram/src/app.scss index 237a53a..55d4031 100644 --- a/miniprogram/src/app.scss +++ b/miniprogram/src/app.scss @@ -1,8 +1,18 @@ page { - background-color: #f7f8fa; + --primary-cyan: #00f0ff; + --primary-green: #00b96b; + --primary-purple: #bd00ff; + --bg-dark: #050505; + --card-bg: rgba(255, 255, 255, 0.03); + --glass-border: rgba(255, 255, 255, 0.08); + --text-main: #ffffff; + --text-secondary: rgba(255, 255, 255, 0.7); + + background-color: var(--bg-dark); font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif; + color: var(--text-main); } .container { diff --git a/miniprogram/src/assets/AI_service.png b/miniprogram/src/assets/AI_service.png new file mode 100644 index 0000000000000000000000000000000000000000..b84a007efcd08a9ea0a68db30bdf0bac8beb7cea GIT binary patch literal 3489 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 IM6N<$f*E`{hX4Qo literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/VR.png b/miniprogram/src/assets/VR.png new file mode 100644 index 0000000000000000000000000000000000000000..c22d66a625448f6e0907a2fac35c33d978912798 GIT binary patch literal 4202 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 literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/VR_active.png b/miniprogram/src/assets/VR_active.png new file mode 100644 index 0000000000000000000000000000000000000000..12555b1d11be91f2d16fe1892fb17886de366fdc GIT binary patch literal 3744 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 literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/cart.png b/miniprogram/src/assets/cart.png index 7484fd03a06f7f172ac8a4dbcf0a265ed97ddd3f..2f7aff507c8d52d8a9eb99a370a0753f1acf7a68 100644 GIT binary patch literal 3745 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 diff --git a/miniprogram/src/assets/cart_active.png b/miniprogram/src/assets/cart_active.png index 270e7489cb3ba9f778a26d807488bb80642b339f..958df53c0d7fcd86de0794a12d8b6c5330f0f753 100644 GIT binary patch literal 3881 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 literal 79 zcmeAS@N?(olH&68a0y~yU~m9o4kjR}%Kfa<)5S5QV$R!xjJ!aBLkqt9=kl|Q0GTlG WAfl^Lw82EZ^t*2wa`*WTnZ{5CqAx z?V0Z2+5Y3gNrL+guDB3_@c&^mQI8nRr@XaMgPRf7ABQVvItf)W6P>-^)oRK}oE&}Z z1}8Ix3VMj`Zq1X7P)E}{Hn z`;G{U5W0|hTC2+%MoQ(QZJihpDk7dSTLNP1*D5r8Vga9;=x|{HPqAMii%=F8=dNJl zKdzTRSvY7fNdjV4edMJgi0}7YScPyE=|Y;UwaqXe2%r%oxF1G>olbQl^^^pPGJRe; zk%oc2pIu$L_(qXH7bdZ^$D)Aa^#^S^_7F-?$`B4J(1lTzDh8Su81Gu^-uYAL;ikFs zbs7Wl4cj+~D_IcA%er3^Aego}uM~Pv^S6%;&ZMGec0Q(9T(6)pWOeC!@yLc#QTdY% zVjGdurcgO8d(|fawogmH^V}kPj!z!m>@d4BaP*6z^G?kKis#K;QyvjqnXnh81J--o zxK4Lod2BmF>FbG`)V2ZXIGhp-34xHnr8j+qM-S+uMO$! zmE!&TK&EO0*SB|bCwZ0R4^cI-G!*=A_j;yqeE#HJ-=O*259j3;JH-CaLfG#AO$g(R z4+r8Rkk{`*MJ+DV5wVRe(P^VxOBt&-LfmYylTNaiY}(V=T%IYKM@zdu1q$am)9)6$~z}D>B7V+Sdr*RkU(67VyyvpMg>W_4ozcuQ6Q(H%6 zcr2^#of_EP?P8Q_IHectwzs?x>RjEqQMK4G>3(`0;?@;2eyg$EPGQVp4fWA2-gI|eK)c665Id<+W&t5PBc7cu zwI$cgf*<5~wTARF0=le&PNeK1a}Y0kDwhTq@A$&A$f4Tcw9J<|w58QwKa90%wD^d2 z$2FHLGb82&o-6u_QpXf;>WiJGwiJ@L-=Gj$Dw5l-E_1#ThhBxc7GUKwYirj8~ho;naTkhiC zE$F}DYL+|ZUN#nR0v=k)*xe!3dqLDMlsIa4?DBUr{)P0>kUk`- zBi}3AOcbfi1pR+z)$ZsmQb$7#RWq(RngrryyNQ&ur#~v()GyTjYWs5HZrVTb4(Ia9 z=>>7s)I5u}Cbzt|`%O|UzA6^$KQqA}^z*UC78>PQw*69mV9sexcyec^rwy_{1s6j* zV94HxT8~X<4c2ip2|0_yapSY8M z^H^yqs;fCpM;!wLcxg#9ArwnG ziNUvmH-o1nQ4S3RGi)vX8}gFU<1Nfc-2x~6AK!HN5s8WfGddg~bhbaU zT@}oENui3CDF*KF-KG^rhfp#-B()Kc1%0x9uR0cI;c}y64#L1&p$y+Yfx9oy^Bm>D zDT$uH)r5+EreZq8hmCL}ty&3`>JR9q2RMrTO+MmmjKvWu)c;l6-#_wx0SD|bGLDx4 zJI`|Rm}h|~_!#*94JmNB z2b3f-7AO7*^S646H7pKS7&CG7+hl}+FW#OVJE@F;@2*u;ehg?cMKWt^y z5Vq*HSJt*4w*;y1?_kd>U~#j!4dP3g5Z}=&f)EFW>CcR`gXP+Yvv9(Y{;(KO;`>>| zFANY;12$bF58@}8$Upe1n?Sj5)I48|@1okjXCQu#AUQQAOcFQ`8^F6;LCwuSGCWX_ zJ0SzH1?mWn|NCGM;FhfL$tyfv=&t)+w4j9AQxl;;S>exJb6Hk}($xlb2p{ zgz+tVS5DBwx}%>~rc7&TP@X2mE24A6rErKo=WF3~PdEFT(}TFPy*y`{LaTv6aZ{~E zv8$5JL~Hzg!qoS&z<8c&V$-XplVh`mzLQOP_)%ugOZm`vogreJ`qr+KuZS^GE1Z$r z6NC0U&~uFH6uJ@?C%WR^`U)f<^1+kKn)pNTX^N^VXni0troOZZJO%JbA3iW zu&Uf?RN0_{f!RCF9)<$!49teq2E%_UQN62ibygVoKzD(HA~3^j(|Z05tOHiR*g68( z6G&;UgP;XDT@z6<*i{S!t*&0&O0Eano#cq7gc3G#?yfWVzZo?Vn!hUFA_>O AvH$=8 literal 140 zcmeAS@N?(olH&68a0y~yU~m9o4kjR}%Kfa<)5S5QV$R$1hP(_0JO?&3$=36*Zaln7 kMA_q+thv>LaONlq;4P+2?+)^JJihtE)78&qol`;+0EB-fumAu6 diff --git a/miniprogram/src/assets/home_active.png b/miniprogram/src/assets/home_active.png index 270e7489cb3ba9f778a26d807488bb80642b339f..5f3a3e8817eab396820e69be76dd54b66ffc491a 100644 GIT binary patch literal 10496 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 z≪#jQCxrg;jM0$!AS>1!P8)v%5isJB+zyWB&Z8c&q-X%^WA@-ZI~B zZC~BEo5@9cAS;599v<>!D=+OfN1x|6Ua<{pywZ3-(kVsUg*4xonpr~#XI6=k9e2f6 z&lN(rzDo&7{{ND0CntNjzeyR$9+ufi_d8=-FD{df``uQx(sg`ft>L?6`Ij=44L)N0)21dZDRUV#eE?3DlR0!jWqf2x&!GA(eJsy>{lurP+2dZ^K8 zJkQSC&tR2N3A4XRRlRX5#wPjv5JJmrL$`$te>IkbGLO(z^)`}=vkJx4c^^&Qh#Erp+qR5WmgR9>N=Gv2jZK?2J-d@Z zuT+M7!3J9PAQ>sYF--CW+q!bTi`ItMsp?PLtg-U7RjXF@Cy+J~!}=ccwOk(08h5sq z`Vhk3cWFP8L0{3ypjYzWU!92Sn5o6L&7?)i+ElL`Nb-v$my~7ss%$>rPqJ)7u#Kwv z%>=P`VzNR+7IZr%3!)xOx#c`Cv%ykCqN;w|Id|%KP{5@X!SIoCBoVvH+t$Ue{VS=+)T z&bhaaljR$W7*~7mKb{^CGi*x}X$+WygNboY9!K@Xv7n|xM4r{-fF5j@awRz{#<;{e z_l6i_+scsfK*Jd0z0SFJ*u&~fUVzNBLmVRoom9Hds&llPocNqBSCHg#`)E(L~TsI{^ZSb|2Ym)J*WB;vV1H-@RkMJF~)d}bM7Fs8NCoAp|vr_c!KwSMOD8}6@WNd+p(?AEdV^L%Zodb zAo+x4P+KH-k1_txIrpLo7;qYbTd3+OwOZ|lc1FC}Y`(BwukTQnC2O@&TC*(6o+qeQ ztEI`2vMeRr4*;71+$ADgT==P{p4xuiym@mg9KSYKr)^>+4?sf*#}q~JQB{q$FWQ=t zzi`fdBDcf2+q-(aI3!ENH`Br~R>A$dNu)j#9PudtShj3gtxf&XP9cOLZiCH=pXauT zrM^S55keRaAq_q7<~*NdV!-z1}~*fuT8a?e_=HrrC6l1b|$0EVh7k9LaYGkcWM5DP_QURom9 zd+#u^9`LCB%9!Hk08TMZ761}sJPE+XnQfQ_0IOH8764eZXpxwznlB7(zf%bRI?sBU;cFW{xq&kBG>tV~mN=PDIvLVuWZ;%(XGbO(IgW$#Vc+ zN^-u4Jg%x20XSAwb-{uKNvaaz=X>w#sybRn9Aiu=h!s}dBLL{VhpKKYCXf`9qA1dU z-2{kH#2A-4=iYAK##rwF*|Q=UVlmSNTZnlvHcwQ4cg`Wk2qSW`S*t3=&(U1iH6et1 zy!S89&fgxuaR9ze^0fdKI_J<%$;6n1h@&ZLdyZYRc}ZVa_X%s-lNtx$VUhze z#)awn*%)JTgtFtE5hqD4i=vRqxho3*E6o;gJUb#HbpW$tj8Eq`?131|opZ;E$i^!4 z(he1{oI*4X3l^^|ch0@D*=%ZGUmu&z=IBX%eSOTlvn`&budh#<%_hC~dx*%D0A?A1 zX!lsz=6RD{yTv}$=c+sB-kEHK_ntAv7dz+PpVqo}&N)@RMMQoA;Ox-=&U>e->i~Qa zz-IHl?h;kK+&OpRc!dUb5oxtrFRa(=t5o%I?|t8Rgp4t2Q4~JL_@Hy{ND)~x7M7Fa zVll=$opbwdqmbiOB636iDkZDL*%yFQ)gsgN>%I3^w8Pn2t=7S{TJ0C+h|qgqnEQKu zjIn7Imx)0y@!oF%wG<@pz({Edx~Mks^?H3T5xGZI*8|wYI7~W8Mo|<+jPViY++TG` z3`vp`LRbf&FOIOAt5)4l6u8ZTwZu$=R`!X^%bKUES9$Meo7%~*F$~};0Kq)0{=vb) zV(#3zPsSLJPxIxRtHl^wMN!-e;1K7WBz2Z-MmuovCJ~8UsGkHb9zgQy7~?$vp54i~ zXKHGtbM7b+d3Zcx_{93H)p{8Zv9vtm(kF0!9}1AI?0!=XmdD8x)in zE}A`90bn!9onwqYb2jFRoS4KflgNz;%5te0nL{Stku%CZHazIsITDk1a zBqheUcU8i@_YbS;KLR+|IR{l;mnPuf0DRfFqZ2`#8NpYO+;M1VXvx69z(0HMYsroz zIK<+NpE>8=+~42-csInTD@3HJsx|Wz+XjAF(6`n8eBT)VQ$*?{pRU*I>(aA| z$g)PGaZphd%batwV~n|~TV<1cWXADV0eFe`zTfPJGmWy;drwu}Gjp3=jFHZ{M*%cU zLgBq1N{u$(R@D!C?-z8ElFS$lsOo#>&YgRKs$LA>0{~hhHs5K4_RWm!H^6vb9P z3&|DMhVB~9zb7e{ZQ3bzChV=sbgu3HxH;a zd9JeJgSpmom$r2&c|Xwx(A14(D>ZBiAq<8PHir;4g%FxCM&<<>A%uqgHu|weEjNXc zLI9?Z6((%XK7%ATk$hLB$|Q9{du-nBdTt&mD{WLeo>6^Z14R?NgfU0C;}W`O$(zZ!ymO-ur`!qNv*`KLaouz)LD>@h1B^AHdEP#!FC5JqNdotk~F#$Y(+b z>b*Zdi#TH@>dDiJ&bhP7viy;V{1m{Cs@+hNO_2iFH3e;E+`Wig;hg(itJQM#dR+nZ ztLl{io}DH)w<`e7^xpp(fNM6Jp(u(&0DLPQn?S@E<3j+jWXTc&z>!BDnZ~j>e@~eO zZ`#+_w>rl7?rhlCh7ev_6h%GtgxEaYXxe9&TCFx0^Ezp=gP9Y z%{lj3@BKmTjC_o-6=S@rD2i3ar9NfGCPR!2jh&;ayJouffK31nNew?Cgqv%%+WRus zxE5o4swj#(?K-WvA>1$R^?oRdVnLhUo0S=KpHyz6 z=+GwZwb>PXJz%x2=iDTJr|;+)9FI7v)Z-X^TfKyLbYCn)m)eQ?DSK(1WR*S19bRP;{$2~Pn?+IF-pzWmMOF+@Oy^>eSuOCXJ-n5z?Wc$HzhCjJB&&H| zmzRy^3!+u>EhlG1fl89CR_lm#tq{Twv&^d6Yh&m4{HD*z*n>&nOz_^X0q{!;I9`@uxRirU;I&-P`Kv)qzb$r#s2zRt3j zeRl4xBJwoJc`?SzoO8);`55CbilR895*oy5d(-D6)s?F1`@Q!Uc6(l)^r>bAyV+Fr zN$>qa5!sXq+w4uHZ!U`BijB7yx~dj3h{!CG`*b*ls``M447J0#k~G@l zGruzAb`*{?q~Q4#e9c=D>hEKW?^4y9opaX!xJg8A?(j@c0l0Q7RJ-aF$qaj)QXRnV z#wc5jt=Vi!%KY{9_3a#Eyjn!Ik1;ZfEhNS`?7cs(*=(-u>+6$o-15x;9?i->oruH` z!cy=3k(q=LKqQ%pB}Sut5qT8AQx<11@8C`KY?TGySn(dU(P+GB_UzefWu~YrL!Ce9 zz5kM}J5qGBG}Jhif$U;6Jm;J{LPTyMSrd`6(P%ucR;%4*jjGay2$~RDbA2b}b*k!+ z_kMwhY@BIE{WbQ0wQYIdvMe=s^;Pv7&bj`|q^%c`pOZY)d%x>MmlNr*t+zS@nJEN` zW{58VuqK26TSN^8nM_BPkd{(?FxiU%~15N#5(u06qa=A%H;uUlEaClROc?8vvXN;AI#|AD?R#Ehhmu z-n_G)1Nahv?ExGwA{S>X5WUj&?_m)7uqDC<0h}%(O&iOL0DRa|tQU&NO(frB_ox|{ zUoIk7+G?mDvwd|7{w^T-ag5Y;)ordnWRT3qOnPPkxBfJb&6Pt-6$&Nk%&MBLHs8IF117B67A(@+Sbi z7{HaCoSFnMipUcr-)s)TR{(rJ8^>Exw^b(50C0|o{2-H%J^-slQzhi90KP%;X8`sG@O9&`{|n%~#Dwm&EEbW6GCV1g+(|?>kn9JrH-I&!qNGwUd@z8GB66?&zYBm}0NiJ|*J#<2 z-G9JhjtwHR+}_SJ&!k)c+-^d!m+e!#*2Yw1@&5s6R>EjUcGH#s0000 - - - - + + + + +Created by potrace 1.10, written by Peter Selinger 2001-2011 + + + + + + + + + + + + + + + diff --git a/miniprogram/src/assets/user.png b/miniprogram/src/assets/user.png index 7484fd03a06f7f172ac8a4dbcf0a265ed97ddd3f..f8c339930de1ec24b0ac79b6a08a03e595d92f8f 100644 GIT binary patch literal 4204 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 diff --git a/miniprogram/src/assets/user_active.png b/miniprogram/src/assets/user_active.png index 270e7489cb3ba9f778a26d807488bb80642b339f..290c90908a78f4822c85b8cdef8ddc68ad903bf6 100644 GIT binary patch literal 3533 zcmV;;4KnhHP)Q;1{96M!>X6FF+0| zh^iwLwCxFw+gX$>PVAa$^ohP@U^u(*oGB-*J0m~}GnBx{P;h&-Vk0aFW996kWmsX} zI|5SZf$_`M&zEchLAx3h&(!@ht310v=RXw?AfGzi{p}Qt+c*Wvts*za0^~*z9s&-| z7Bnw22E%$~0_BN10%Ru)z{oG(QNY+mzwHtfy`OASeEuasK6Q8l94w4Tvqp;iZbL{Q z#~Sq^@kNkp0`et<_5)S}df`_dY}B9t;h-$s;xKLt4+yIO=?`BB(BM&{Je*k{{TAX} zXh4{KWLa=PgcprVuCS1V1w?SXK;r%k~1cE2Y33j;`+JxnIy zf=-2b(1ZXa!4W2@BY=aZ>xgk?j}Zb8h-?84xZ$dCspbAlpnE{@qzkiNo`9eG2akI| zP&Dq7h!|%z_p^v=Ks0*{;l}j?$Tc7+57)^uv@?y}5i@L|j*BnnA9RCHXi{0W5J{ zLE>r#@s^x4kW%*jz1fy#aE?f|AdM74Qa#yRRsQYv-A3u4Zv> zfVfJ|vkKJtaSC_?WSnvu>8Sz+K9)T7?dT6~0P&QRqe;~D{SbTtAHm! z&Q(UewJ6}`VI@FX%0Tx%Q^3vFL;>?$BY?B+(E8Bo9rAO@tA3mz5l1jsLs3A$)ZZgSxXkc-xm+_tQM z0I{4M`8Hhv0Wv)WE*<#SOH`f!>68>Nsa6~{3U~s<&5L18B8}bSCKsLnF;*jT$AbcH z09lsB%~R{@h2<$1ZU9L|B}kGG6F07M;0=&#y{J#D;*LYR%7HgPTs^%y7IDw6UFE

bmn4a0iIDDZV!4Gwy@- zA2Y`7Ii@>6l2}@1B_RfGT+ZBH0dbkEfyJ(&k^(LP!Bo!~gqFh8rndsn+}-kunz;mo zfx$G3K!icft=4Vyq8W08ubNz%vd^4MSZQTQcCmd%G+RQ@oIQCCy>w5NJ(%b{WAYtyG zbck_i>jTHo5Q+v10SJ=G8AOm2l|<1+VYCbZ2vZJ7xEvrTJe1Wd@GEIq-@}FO3Jiud#l;8;%Y)NqHP06}s&gM>=($+Z{m0TT)k7E6L8T%s9!qh0%}Rb}wu0HIER zgzqDQgkqFgNH{1>NI)2Pf`m&h9?G685Ec$f5*82!4@mevB1kC4V+-}g>X8JY0ihs* zgep8V!odf~!yluZ3=aqsogmRW3B~v{E~~=BK`BZA;dSl}km$wzGs}d7T6`V_|673U z3P6y&365Poa94m(g`FixqAf4@p#q^VnmKm0z+C}C6>bcb!WYzcoR08b!dMaVLP!v@ z+Fk(xvRC6~A63IAs%Ji>_ALUWG5;vNJt!7t6@mb_7QSOF_`~DIDC6Gl7m&d@ zMIjOtTAo!#!a@iZgbX*f#$z0+wJIP$S`~b7KT4FK&gib1Ox=^*#t;W zbzk(265k3OTY~e<@-+hCg_b>k5+FU*Ro|CaK!8-FP`AWUK!C)_wIZ!h;L?Ds>-v3X zAK2fw-#C!1>nIJ@^oM!Kw&kayF3sF>DL{|_3XaXXB^@fRJNtqC?>ob;+CT-e_RK@J zEkEo3kZ#}%^r?9$(||x&0zt#>`Ww+wa3%t~Y8O;s0|&|E0}h*&|E|q+80G+hY8i+X z%4LfJ1wszfnA0D~w`o{R0Rpl3L?ILuj~1p{f(Oc6XINMRX5=sd2*H4H3B@Vcj5u;+ ziUOuUnF9Wc3|Y?)E8xRAO&^`Oa)~)Ab^8wOo6ehfHAw5 zABCW>c7_Hr6cE5cFtnT+nqpUj4Toe1AOIu3Yw&A@tBfg_;R+BEl+-$;mbwFiNEpZp z8M(zqO$szCz*<6St=*SgT>(J^3}9`RLmfzg#|m&ZVU0ch^@OwC07-D@edB~AneSq* zgIyu%0*K(4TQLDg?*_@~fC!F&jkN_=aZ}HUlC!o=L0olNC7SrM(#th@hDRBB+^Ept&}i07C67%f*ls17apRIU)tx6)0Y29RZ}c({}&WgB0kh zfM$@c@~ZPN3KY#CN7ig%lvALWR-m+|ivYQ_68}4|Jt=Tlfl@$<*KCJ9&<#?+umYuk z7!Esscq1eE~8xM;hF^0!4r<%Th#}^iOQPhD{h1%8&gc?6K+PD=;ZbyC2(0!IL`9*g{w0!0eo z{iFDQs8b9GI^`0JR^)^(k^)8*I8?*MfTUox%t}IN^HKrJKOXv5Ax8nR9JqXw0woIc zn?bb9D$!rpSXLm_aIuS;L5ii)g$@)r{EAQm$l*a82&)hrD1h0c`0G^;_b?zBgljlj z?n7HCFi?T&p{gG2hXJ8`fMnrTCIzmd0BZK|?7`To0O5_WDk5>8niLqVKy|HF15!)m zXk|2>6lkf{YCw|gT52UBv@xqd_3Xijng9Wrd|9WlFPRiDrvPfS{N3Y$_f3GL{+b9) zeP|;EdMMCbo6UfLP|hIDj`UDReTG)xCu*~4_Mm#rfUuL~2tnbYObX0a;8&{2W;?$36h2vclIKa0(T0av=%Pq?|wM*M0Y^w zEF$55xOvC^sb3h-pISTxwpwCMjR<`RAUO~OwP0T_?+`()N&zmZSVL7e*W5G|5IP7* zI4ueiiXkgifGZD_&cfO$z2}tch66&c5$OkvOD=&zL1eEK*k%pX3n6)RU6(null) - const [loading, setLoading] = useState(true) - - useLoad((options) => { - if (options.id) fetchDetail(options.id) - }) - - const fetchDetail = async (id: string) => { - try { - const res: any = await getARServiceDetail(Number(id)) - setDetail(res) - } catch (err) { - console.error(err) - Taro.showToast({ title: '加载失败', icon: 'none' }) - } finally { - setLoading(false) - } - } - - const handleLaunch = () => { - Taro.showModal({ - title: '提示', - content: '请使用摄像头扫描空间以启动 AR 体验 (演示模式)', - showCancel: false - }) - } - - if (loading) return Loading... - if (!detail) return Not Found - - return ( - - {detail.title} - {detail.description} - - - 📷 - AR 场景加载区域 - - - - - ) -} diff --git a/miniprogram/src/pages/cart/cart.scss b/miniprogram/src/pages/cart/cart.scss index 4de2841..1f4e69f 100644 --- a/miniprogram/src/pages/cart/cart.scss +++ b/miniprogram/src/pages/cart/cart.scss @@ -1,8 +1,214 @@ .page-container { - min-height: 100vh; - background-color: #f7f8fa; - display: flex; - justify-content: center; - align-items: center; + min-height: 100vh; + background-color: #050505; + color: #fff; + padding-bottom: 120px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; +} + +.empty-state { + height: 80vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .empty-icon { + font-size: 80px; + margin-bottom: 20px; + opacity: 0.5; + } + + .empty-text { + font-size: 28px; + color: #666; + } +} + +.cart-list { + padding: 20px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.cart-item { + display: flex; + align-items: center; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 16px; + padding: 20px; + backdrop-filter: blur(10px); + + .checkbox-area { + padding: 10px; + margin-right: 10px; + + .checkbox { + width: 40px; + height: 40px; + border-radius: 50%; + border: 2px solid #666; + display: flex; + align-items: center; + justify-content: center; + + &.checked { + border-color: #00b96b; + background: rgba(0, 185, 107, 0.2); + color: #00b96b; + } + } + } + + .item-img { + width: 160px; + height: 160px; + border-radius: 12px; + margin-right: 20px; + background: #000; + object-fit: cover; + } + + .item-info { + flex: 1; + height: 160px; + display: flex; + flex-direction: column; + justify-content: space-between; + + .item-name { + font-size: 30px; + font-weight: bold; + color: #fff; + margin-bottom: 8px; + } + + .item-desc { + font-size: 24px; + color: #888; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .price-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: auto; + + .price { + font-size: 32px; + color: #00b96b; + font-weight: bold; + } + + .quantity-control { + display: flex; + align-items: center; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + padding: 4px; + + .btn-qty { + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + font-size: 32px; + color: #fff; + + &:active { opacity: 0.7; } + } + + .qty-num { + width: 60px; + text-align: center; + font-size: 28px; + font-weight: bold; + } + } + } + } + + .btn-delete { + padding: 10px; + margin-left: 10px; + color: #ff4d4f; + font-size: 32px; + } +} + +.bottom-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 110px; + background: rgba(20, 20, 20, 0.95); + backdrop-filter: blur(20px); + border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 30px; + z-index: 100; + + .left-section { + display: flex; + align-items: center; + + .select-all-btn { + display: flex; + align-items: center; + margin-right: 30px; + + .checkbox { + width: 36px; + height: 36px; + border-radius: 50%; + border: 2px solid #666; + margin-right: 10px; + display: flex; + align-items: center; + justify-content: center; + + &.checked { + border-color: #00b96b; + background: rgba(0, 185, 107, 0.2); + color: #00b96b; + } + } + + .label { font-size: 28px; color: #fff; } + } + + .total-info { + .label { font-size: 24px; color: #888; margin-right: 10px; } + .price { font-size: 40px; color: #00b96b; font-weight: bold; } + } + } + + .btn-checkout { + background: linear-gradient(135deg, #00b96b 0%, #00f0ff 100%); + color: #000; + border-radius: 40px; + padding: 0 50px; + height: 80px; + line-height: 80px; + font-size: 32px; + font-weight: bold; + border: none; + box-shadow: 0 0 20px rgba(0, 185, 107, 0.3); + + &:active { transform: scale(0.98); } + &.disabled { + background: #333; + color: #666; + box-shadow: none; + } + } } -.empty { color: #999; font-size: 16px; } diff --git a/miniprogram/src/pages/cart/cart.tsx b/miniprogram/src/pages/cart/cart.tsx index c70933b..45433a2 100644 --- a/miniprogram/src/pages/cart/cart.tsx +++ b/miniprogram/src/pages/cart/cart.tsx @@ -1,12 +1,145 @@ -import { View, Text } from '@tarojs/components' +import { View, Text, Image, ScrollView, Button } from '@tarojs/components' +import Taro, { useDidShow } from '@tarojs/taro' +import { useState, useMemo } from 'react' +import { getCart, updateQuantity, removeItem, toggleSelect, toggleSelectAll, CartItem } from '../../utils/cart' import './cart.scss' export default function Cart() { + const [cartItems, setCartItems] = useState([]) + + useDidShow(() => { + refreshCart() + }) + + const refreshCart = () => { + setCartItems(getCart()) + } + + const handleUpdateQuantity = (id: number, delta: number) => { + const item = cartItems.find(i => i.id === id) + if (!item) return + const newQty = item.quantity + delta + if (newQty < 1) return + const newCart = updateQuantity(id, newQty) + setCartItems(newCart) + } + + const handleRemove = (id: number) => { + Taro.showModal({ + title: '提示', + content: '确定要删除该商品吗?', + success: (res) => { + if (res.confirm) { + const newCart = removeItem(id) + setCartItems(newCart) + } + } + }) + } + + const handleToggle = (id: number) => { + const newCart = toggleSelect(id) + setCartItems(newCart) + } + + const isAllSelected = useMemo(() => { + return cartItems.length > 0 && cartItems.every(i => i.selected) + }, [cartItems]) + + const handleToggleAll = () => { + const newCart = toggleSelectAll(!isAllSelected) + setCartItems(newCart) + } + + const selectedCount = useMemo(() => { + return cartItems.filter(i => i.selected).reduce((sum, i) => sum + i.quantity, 0) + }, [cartItems]) + + const totalPrice = useMemo(() => { + return cartItems.filter(i => i.selected).reduce((sum, i) => sum + i.price * i.quantity, 0) + }, [cartItems]) + + const handleCheckout = () => { + if (selectedCount === 0) { + Taro.showToast({ title: '请选择商品', icon: 'none' }) + return + } + Taro.navigateTo({ + url: '/pages/order/checkout?from=cart' + }) + } + + const goShopping = () => { + Taro.switchTab({ url: '/pages/index/index' }) + } + return ( - - 购物车功能即将上线 - + {cartItems.length === 0 ? ( + + 🛒 + 购物车空空如也 + + + ) : ( + + {cartItems.map(item => ( + + handleToggle(item.id)}> + + {item.selected && } + + + + + + + + {item.name} + {/* {item.description} */} + + + + ¥{item.price} + + + handleUpdateQuantity(item.id, -1)}>− + {item.quantity} + handleUpdateQuantity(item.id, 1)}>+ + + + + + handleRemove(item.id)}>× + + ))} + + )} + + {cartItems.length > 0 && ( + + + + + {isAllSelected && } + + 全选 + + + + 合计: + ¥{totalPrice} + + + + + + )} ) } diff --git a/miniprogram/src/pages/ar/detail.config.ts b/miniprogram/src/pages/courses/detail.config.ts similarity index 100% rename from miniprogram/src/pages/ar/detail.config.ts rename to miniprogram/src/pages/courses/detail.config.ts diff --git a/miniprogram/src/pages/ar/detail.scss b/miniprogram/src/pages/courses/detail.scss similarity index 67% rename from miniprogram/src/pages/ar/detail.scss rename to miniprogram/src/pages/courses/detail.scss index 8f35c4c..1adc137 100644 --- a/miniprogram/src/pages/ar/detail.scss +++ b/miniprogram/src/pages/courses/detail.scss @@ -13,6 +13,28 @@ display: block; } +.meta-info { + display: flex; + flex-wrap: wrap; + gap: 20px; + margin-bottom: 30px; + align-items: center; + + .tag { + background: rgba(0, 240, 255, 0.2); + color: #00f0ff; + padding: 6px 16px; + border-radius: 4px; + font-size: 24px; + border: 1px solid #00f0ff; + } + + .info { + color: #888; + font-size: 26px; + } +} + .desc { color: #aaa; font-size: 28px; @@ -20,7 +42,7 @@ display: block; } -.ar-placeholder { +.course-placeholder { width: 100%; height: 500px; background: #111; diff --git a/miniprogram/src/pages/courses/detail.tsx b/miniprogram/src/pages/courses/detail.tsx new file mode 100644 index 0000000..ab29a86 --- /dev/null +++ b/miniprogram/src/pages/courses/detail.tsx @@ -0,0 +1,70 @@ +import { View, Text, Button, Image } from '@tarojs/components' +import Taro, { useLoad } from '@tarojs/taro' +import { useState } from 'react' +import { getVBCourseDetail } from '../../api' +import './detail.scss' + +export default function CourseDetail() { + const [detail, setDetail] = useState(null) + const [loading, setLoading] = useState(true) + + useLoad((options) => { + if (options.id) fetchDetail(options.id) + }) + + const typeMap: Record = { + software: '软件课程', + hardware: '硬件课程', + incubation: '产品商业孵化' + } + + const fetchDetail = async (id: string) => { + try { + const res: any = await getVBCourseDetail(Number(id)) + setDetail(res) + } catch (err) { + console.error(err) + Taro.showToast({ title: '加载失败', icon: 'none' }) + } finally { + setLoading(false) + } + } + + const handleLaunch = () => { + Taro.showToast({ + title: '课程内容准备中', + icon: 'none' + }) + } + + if (loading) return Loading... + if (!detail) return Not Found + + return ( + + {detail.title} + + + {typeMap[detail.course_type] || '软件课程'} + 讲师: {detail.instructor} + 时长: {detail.duration} + 课时: {detail.lesson_count} + + + {detail.description} + + + {detail.display_detail_image ? ( + + ) : ( + <> + 📚 + 课程大纲与视频内容加载区域 + + )} + + + + + ) +} diff --git a/miniprogram/src/pages/ar/index.config.ts b/miniprogram/src/pages/courses/index.config.ts similarity index 100% rename from miniprogram/src/pages/ar/index.config.ts rename to miniprogram/src/pages/courses/index.config.ts diff --git a/miniprogram/src/pages/ar/index.scss b/miniprogram/src/pages/courses/index.scss similarity index 71% rename from miniprogram/src/pages/ar/index.scss rename to miniprogram/src/pages/courses/index.scss index a0a4c70..179338d 100644 --- a/miniprogram/src/pages/ar/index.scss +++ b/miniprogram/src/pages/courses/index.scss @@ -68,6 +68,33 @@ font-size: 80px; font-weight: bold; } + + .tag-container { + position: absolute; + top: 10px; + right: 10px; + display: flex; + gap: 10px; + } + + .type-tag { + background: rgba(0, 240, 255, 0.2); + border: 1px solid #00f0ff; + padding: 4px 12px; + border-radius: 4px; + .type-text { + color: #00f0ff; + font-size: 20px; + } + + &.special { + background: rgba(255, 87, 34, 0.2); + border: 1px solid #ff5722; + .type-text { + color: #ff5722; + } + } + } } .content { @@ -79,6 +106,16 @@ margin-bottom: 15px; display: block; } + + .info-row { + display: flex; + gap: 20px; + margin-bottom: 10px; + .info-text { + color: #aaa; + font-size: 24px; + } + } .item-desc { color: #888; diff --git a/miniprogram/src/pages/ar/index.tsx b/miniprogram/src/pages/courses/index.tsx similarity index 66% rename from miniprogram/src/pages/ar/index.tsx rename to miniprogram/src/pages/courses/index.tsx index 6c5c3dd..b984c0f 100644 --- a/miniprogram/src/pages/ar/index.tsx +++ b/miniprogram/src/pages/courses/index.tsx @@ -1,21 +1,21 @@ import { View, Text, Image, Button } from '@tarojs/components' import Taro, { useLoad } from '@tarojs/taro' import { useState } from 'react' -import { getARServices } from '../../api' +import { getVBCourses } from '../../api' import './index.scss' -export default function ARIndex() { - const [arList, setArList] = useState([]) +export default function CourseIndex() { + const [courseList, setCourseList] = useState([]) const [loading, setLoading] = useState(true) useLoad(() => { - fetchAR() + fetchCourses() }) - const fetchAR = async () => { + const fetchCourses = async () => { try { - const res: any = await getARServices() - setArList(res.results || res) + const res: any = await getVBCourses() + setCourseList(res.results || res) } catch (err) { console.error(err) Taro.showToast({ title: '加载失败', icon: 'none' }) @@ -25,7 +25,7 @@ export default function ARIndex() { } const goDetail = (id: number) => { - Taro.navigateTo({ url: `/pages/ar/detail?id=${id}` }) + Taro.navigateTo({ url: `/pages/courses/detail?id=${id}` }) } if (loading) return Loading... @@ -35,29 +35,29 @@ export default function ARIndex() { - AR UNIVERSE - 探索全息增强现实体验 + VB COURSES + 探索 VB 编程课程 - {arList.length === 0 ? ( + {courseList.length === 0 ? ( - 暂无 AR 体验内容 + 暂无 VB 课程内容 ) : ( - arList.map((item) => ( + courseList.map((item) => ( goDetail(item.id)}> {item.cover_image_url ? ( ) : ( - AR + VB )} {item.title} {item.description} - + )) diff --git a/miniprogram/src/pages/goods/detail.scss b/miniprogram/src/pages/goods/detail.scss index fd9973f..b731a92 100644 --- a/miniprogram/src/pages/goods/detail.scss +++ b/miniprogram/src/pages/goods/detail.scss @@ -1,8 +1,10 @@ .page-container { - height: 100vh; - background-color: #000; + min-height: 100vh; + background-color: #050505; color: #fff; position: relative; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + overflow-x: hidden; } .loading-screen, .error-screen { @@ -10,147 +12,259 @@ display: flex; align-items: center; justify-content: center; - color: #666; + color: #00f0ff; + background: #000; + font-size: 28px; + letter-spacing: 2px; } .content { height: 100vh; - background: #000; + position: relative; + z-index: 1; + padding-bottom: 200px; // Ensure scroll space for bottom bar } -.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); +// Animations +@keyframes fadeInUp { + from { opacity: 0; transform: translateY(40px); } + to { opacity: 1; transform: translateY(0); } } +@keyframes float { + 0% { transform: translateY(0px); } + 50% { transform: translateY(-20px); } + 100% { transform: translateY(0px); } +} + +@keyframes pulse-glow { + 0% { box-shadow: 0 0 10px rgba(0, 185, 107, 0.4); } + 50% { box-shadow: 0 0 25px rgba(0, 185, 107, 0.8), 0 0 10px rgba(0, 240, 255, 0.4); } + 100% { box-shadow: 0 0 10px rgba(0, 185, 107, 0.4); } +} + +@keyframes scanline { + 0% { top: -10%; opacity: 0; } + 50% { opacity: 1; } + 100% { top: 110%; opacity: 0; } +} + +// Hero Section .hero-section { position: relative; - margin-bottom: 20px; + margin-bottom: 40px; + animation: fadeInUp 0.8s ease-out; .image-container { width: 100%; - min-height: 600px; - background: radial-gradient(circle at center, #1a1a1a, #000); + min-height: 600px; // Slightly reduced to fit better + background: radial-gradient(circle at center, rgba(0, 240, 255, 0.05) 0%, transparent 70%); position: relative; display: flex; align-items: center; justify-content: center; + overflow: hidden; + + // Scanline effect + &::after { + content: ''; + position: absolute; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(to right, transparent, rgba(0, 240, 255, 0.5), transparent); + animation: scanline 3s linear infinite; + z-index: 0; + } .hero-img { - width: 100%; + width: 75%; + height: auto; display: block; + filter: drop-shadow(0 0 40px rgba(0, 240, 255, 0.2)); + animation: float 6s ease-in-out infinite; + z-index: 1; } .placeholder-box { - .icon-bolt { font-size: 100px; } - } - - .hero-overlay { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 60%; - background: linear-gradient(to top, #000 10%, transparent); + .icon-bolt { font-size: 150px; color: #00b96b; text-shadow: 0 0 30px rgba(0, 185, 107, 0.5); } } } .hero-content { - padding: 0 30px; - margin-top: -100px; // Pull up over image + padding: 0 40px; + margin-top: -40px; position: relative; z-index: 2; .hero-title { - font-size: 48px; + font-size: 60px; font-weight: 900; color: #fff; display: block; - margin-bottom: 15px; - text-shadow: 0 0 20px rgba(0,0,0,0.8); + margin-bottom: 24px; + line-height: 1.1; + text-shadow: 0 0 20px rgba(0, 240, 255, 0.3); + letter-spacing: 1px; } .hero-desc { font-size: 28px; - color: #ccc; - line-height: 1.5; + color: rgba(255, 255, 255, 0.7); + line-height: 1.6; display: block; - margin-bottom: 25px; - text-shadow: 0 0 10px rgba(0,0,0,0.8); + margin-bottom: 32px; + font-weight: 300; } .tags-row { display: flex; flex-wrap: wrap; - gap: 15px; + gap: 16px; .tag { - padding: 8px 20px; - border-radius: 30px; + padding: 10px 28px; + border-radius: 4px; // Techy sharp corners font-size: 24px; + font-weight: 600; backdrop-filter: blur(10px); + position: relative; + overflow: hidden; - &.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); } + // Tech border effect + &::before { + content: ''; + position: absolute; + top: 0; left: 0; width: 4px; height: 100%; + } + + &.cyan { + color: #00f0ff; + background: rgba(0, 240, 255, 0.08); + border: 1px solid rgba(0, 240, 255, 0.3); + &::before { background: #00f0ff; } + } + &.blue { + color: #3b82f6; + background: rgba(59, 130, 246, 0.08); + border: 1px solid rgba(59, 130, 246, 0.3); + &::before { background: #3b82f6; } + } + &.purple { + color: #a855f7; + background: rgba(168, 85, 247, 0.08); + border: 1px solid rgba(168, 85, 247, 0.3); + &::before { background: #a855f7; } + } } } } } +// Stats Card (HUD Style) .stats-card { - margin: 0 30px 40px; - border-radius: 24px; - padding: 30px; - display: flex; - align-items: center; - justify-content: space-around; - - .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; } + margin: 40px 40px 60px; + padding: 30px !important; + background: rgba(20, 20, 20, 0.6) !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 12px; + position: relative; + backdrop-filter: blur(10px) !important; + animation: fadeInUp 0.8s ease-out 0.2s backwards; + + // Corner accents + &::before { + content: ''; + position: absolute; + top: -1px; left: -1px; + width: 20px; height: 20px; + border-top: 2px solid #00b96b; + border-left: 2px solid #00b96b; + border-top-left-radius: 12px; + } + &::after { + content: ''; + position: absolute; + bottom: -1px; right: -1px; + width: 20px; height: 20px; + border-bottom: 2px solid #00b96b; + border-right: 2px solid #00b96b; + border-bottom-right-radius: 12px; + } + + .label-row { + display: flex; + width: 100%; + margin-bottom: 12px; + .label { font-size: 24px; color: #666; flex: 1; text-transform: uppercase; letter-spacing: 1px; } + } + + .value-row { + display: flex; + width: 100%; + align-items: baseline; + + .price-box { + flex: 1; + display: flex; + align-items: baseline; + .symbol { font-size: 32px; color: #00b96b; font-weight: bold; margin-right: 4px; } + .price { + font-size: 72px; + color: #00b96b; + font-weight: bold; + text-shadow: 0 0 25px rgba(0, 185, 107, 0.4); + font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; // Ensure clean number font + } + } + + .stock-box { + .stock { font-size: 36px; color: #fff; font-weight: bold; } + .unit { font-size: 24px; color: #666; margin-left: 6px; } + } } - - .divider { width: 1px; height: 60px; background: rgba(255,255,255,0.1); } } +// Features Section .features-section { - padding: 0 30px; + padding: 0 40px; display: flex; flex-direction: column; - gap: 20px; - margin-bottom: 40px; + gap: 40px; + margin-bottom: 60px; .feature-card { - padding: 30px; - border-radius: 20px; display: flex; - align-items: flex-start; + flex-direction: row; // Change to row for better list layout + align-items: center; + text-align: left; + background: rgba(255, 255, 255, 0.03) !important; + border: 1px solid rgba(255, 255, 255, 0.05) !important; + border-radius: 16px; + padding: 30px; + animation: fadeInUp 0.8s ease-out; + // Stagger animations manually or via JS (here simplified) .feature-icon-box { - width: 80px; - height: 80px; - margin-right: 25px; + width: 100px; + height: 100px; + margin-right: 30px; + margin-bottom: 0; display: flex; align-items: center; justify-content: center; - background: rgba(255,255,255,0.05); - border-radius: 16px; + background: rgba(0, 0, 0, 0.3); + border-radius: 50%; + border: 1px solid rgba(255, 255, 255, 0.1); - .f-icon { font-size: 40px; color: #00f0ff; } - .f-icon-img { width: 50px; height: 50px; } + .f-icon { font-size: 50px; color: #00b96b; } + .f-icon-img { width: 60px; height: 60px; object-fit: contain; } } .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; } + .f-title { font-size: 32px; font-weight: bold; color: #fff; margin-bottom: 10px; display: block; } + .f-desc { font-size: 24px; color: #888; line-height: 1.5; } } } } @@ -158,66 +272,94 @@ .detail-image-section { width: 100%; margin-bottom: 40px; - .long-detail-img { width: 100%; display: block; } + position: relative; + + // Decorative line top + &::before { + content: ''; + display: block; + width: 100px; + height: 4px; + background: #333; + margin: 0 auto 40px; + border-radius: 2px; + } + + .long-detail-img { width: 100%; height: auto; display: block; } } -.footer-spacer { height: 160px; } +.footer-spacer { height: 200px; } +// Bottom Bar .bottom-bar { position: fixed; - bottom: 0; - left: 0; - right: 0; - padding: 20px 30px; + bottom: 40px; + left: 30px; + right: 30px; + height: 110px; z-index: 100; - border-top-left-radius: 30px; - border-top-right-radius: 30px; - background: rgba(20, 20, 20, 0.95); // Darker for contrast + border-radius: 55px; // Fully rounded capsule + background: rgba(20, 20, 20, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 10px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; .action-row { + width: 100%; + height: 100%; display: flex; align-items: center; - gap: 20px; - height: 100px; - .cart-icon-btn { - display: flex; - 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 { + .btn-add-cart { flex: 1; - height: 80px; - line-height: 80px; - border-radius: 40px; - font-size: 28px; + height: 100%; + border-radius: 45px 0 0 45px; + font-size: 30px; font-weight: bold; border: none; margin: 0; - - &::after { border: none; } - } - - .btn-add-cart { background: rgba(255, 255, 255, 0.1); color: #fff; + display: flex; + align-items: center; + justify-content: center; + + &:active { background: rgba(255, 255, 255, 0.2); } } - + .btn-buy-now { - background: linear-gradient(90deg, #00b96b, #00f0ff); - color: #000; - box-shadow: 0 5px 20px rgba(0, 185, 107, 0.3); + flex: 1; + height: 100%; + border-radius: 0 45px 45px 0; + font-size: 30px; + font-weight: 800; + border: none; + margin: 0; + background: linear-gradient(135deg, #00b96b 0%, #00f0ff 100%); + color: #000; // Black text for high contrast on neon + display: flex; + align-items: center; + justify-content: center; + animation: pulse-glow 3s infinite; + transition: all 0.2s ease; + + &:active { + transform: scale(0.98); + opacity: 0.9; + } + + .cart-icon { + font-size: 36px; + margin-right: 12px; + } } } } .safe-area-bottom { - padding-bottom: calc(20px + constant(safe-area-inset-bottom)); - padding-bottom: calc(20px + env(safe-area-inset-bottom)); + padding-bottom: 0; } diff --git a/miniprogram/src/pages/goods/detail.tsx b/miniprogram/src/pages/goods/detail.tsx index dfc6b67..0609e85 100644 --- a/miniprogram/src/pages/goods/detail.tsx +++ b/miniprogram/src/pages/goods/detail.tsx @@ -2,6 +2,8 @@ import { View, Text, Image, ScrollView, Button } from '@tarojs/components' import Taro, { useRouter, useLoad } from '@tarojs/taro' import { useState } from 'react' import { getConfigDetail } from '../../api' +import ParticleBackground from '../../components/ParticleBackground' +import { addToCart } from '../../utils/cart' import './detail.scss' export default function Detail() { @@ -26,6 +28,11 @@ export default function Detail() { } } + const handleAddToCart = () => { + if (!product) return + addToCart(product) + } + const buyNow = () => { if (!product) return Taro.navigateTo({ @@ -38,12 +45,13 @@ export default function Detail() { return ( + {/* Hero Section */} - {product.detail_image_url || product.static_image_url ? ( - + {product.static_image_url ? ( + ) : ( @@ -65,44 +73,84 @@ export default function Detail() { {/* Stats Section */} - - - 售价 - ¥{product.price} + + + 售价 + 库存 - - - 库存 - {product.stock}件 + + + ¥ + {product.price} + + + {product.stock} + + {/* Features Section */} {product.features && product.features.length > 0 ? ( - product.features.map((f, idx) => ( - - - {f.icon_url ? : } + product.features.map((f, idx) => { + let iconContent + if (f.display_icon) { + iconContent = + } else if (f.icon_url) { + iconContent = + } else { + let iconChar = '⭐' + let iconColor = '#00b96b' + switch(f.icon_name) { + case 'SafetyCertificate': iconChar = '🛡'; break; + case 'Eye': iconChar = '👁'; iconColor = '#3b82f6'; break; + case 'Thunderbolt': iconChar = '⚡'; iconColor = '#faad14'; break; + default: break; + } + iconContent = {iconChar} + } + + return ( + + + {iconContent} + + + {f.title} + {f.description} + - - {f.title} - {f.description} - - - )) + ) + }) ) : ( - - 极致性能释放 - {product.chip_type} 强劲核心,提供强大的边缘计算算力支持。 - + <> + + + 🛡 + + + 工业级安全标准 + 采用军工级加密芯片,保障您的数据隐私安全。无论是边缘计算还是云端同步,全程加密传输。 + + + + + 👁 + + + 超清视觉感知 + 搭载 4K 高清摄像头与 AI 视觉算法,实时捕捉每一个细节。支持人脸识别、物体检测等。 + + + )} {/* Detail Image */} - {product.detail_image_url && ( + {(product.display_detail_image || product.detail_image_url) && ( - + )} @@ -110,14 +158,15 @@ export default function Detail() { {/* 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 4bb7997..b6c63f0 100644 --- a/miniprogram/src/pages/index/index.scss +++ b/miniprogram/src/pages/index/index.scss @@ -1,52 +1,79 @@ .page-container { height: 100vh; - background-color: #000; - color: #fff; + background-color: var(--bg-dark); + color: var(--text-main); overflow: hidden; position: relative; + + // Ambient Light 1 (Cyan) + &::before { + content: ''; + position: absolute; + top: -10%; + left: -10%; + width: 60%; + height: 40%; + background: radial-gradient(circle, rgba(0, 240, 255, 0.15) 0%, transparent 70%); + filter: blur(80px); + z-index: 0; + pointer-events: none; + } + + // Ambient Light 2 (Green/Purple mix) + &::after { + content: ''; + position: absolute; + bottom: 10%; + right: -10%; + width: 50%; + height: 40%; + background: radial-gradient(circle, rgba(189, 0, 255, 0.1) 0%, transparent 70%); + filter: blur(80px); + z-index: 0; + pointer-events: none; + } } .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; - padding: 60px 20px 40px; + padding: 80px 24px 60px; // 增加头部留白 position: relative; .logo-box { - margin-bottom: 30px; + margin-bottom: 40px; 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)); + width: 140px; + height: 140px; + margin-bottom: 20px; + filter: drop-shadow(0 0 25px rgba(0, 240, 255, 0.5)); + animation: float 6s ease-in-out infinite; } .logo-text { - font-size: 40px; + font-size: 48px; font-weight: 900; color: #fff; - letter-spacing: 6px; - text-shadow: 0 0 20px rgba(0, 240, 255, 0.6); + letter-spacing: 8px; + text-shadow: 0 0 30px rgba(0, 240, 255, 0.7); } } .title-container { - margin-bottom: 25px; + margin-bottom: 30px; display: flex; justify-content: center; align-items: center; @@ -54,26 +81,27 @@ } .title-text { - font-size: 36px; - font-weight: bold; - color: #00f0ff; - text-shadow: 0 0 15px rgba(0, 240, 255, 0.5); + font-size: 40px; + font-weight: 800; + color: var(--primary-cyan); + text-shadow: 0 0 20px rgba(0, 240, 255, 0.4); } .cursor { - font-size: 36px; + font-size: 40px; color: #fff; margin-left: 8px; animation: blink 1s infinite; } .subtitle { - color: #aaa; - font-size: 26px; - line-height: 1.6; + color: var(--text-secondary); + font-size: 28px; + line-height: 1.8; // 增加行高 display: block; padding: 0 40px; - font-weight: 300; + font-weight: 400; + letter-spacing: 1px; } } @@ -82,40 +110,54 @@ 50% { opacity: 0; } } -.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;} +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } } .product-grid { - padding: 0 30px; + padding: 0 32px; display: flex; flex-direction: column; - gap: 40px; + gap: 48px; // 增加卡片间距 } +// 玻璃态卡片升级版 .card { background: rgba(255, 255, 255, 0.03); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-radius: 24px; + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border-radius: 32px; overflow: hidden; 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; + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.4), + inset 0 0 0 1px rgba(255, 255, 255, 0.05); // 内描边增强质感 + transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); + position: relative; + // 高光反射效果 + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent); + opacity: 0.5; + } + &:active { - transform: scale(0.98); - border-color: #00b96b; - box-shadow: 0 0 30px rgba(0, 185, 107, 0.2); + transform: scale(0.96); + box-shadow: + 0 10px 20px rgba(0, 0, 0, 0.4), + 0 0 30px rgba(0, 240, 255, 0.1); // 按压发光 + border-color: rgba(0, 240, 255, 0.3); } &-cover { - height: 360px; + height: 400px; // 加大图片区域 background: #111; position: relative; overflow: hidden; @@ -123,7 +165,8 @@ .card-img { width: 100%; height: 100%; - transition: transform 0.5s ease; + object-fit: cover; + transition: transform 0.6s ease; } .placeholder-img { @@ -132,53 +175,85 @@ display: flex; align-items: center; justify-content: center; - background: radial-gradient(circle at center, #222, #111); - .icon-rocket { font-size: 100px; } - } + background: radial-gradient(circle at center, #1a1a1a, #050505); + + .radar-scan { + width: 100px; + height: 100px; + border: 2px solid rgba(0, 240, 255, 0.3); + border-radius: 50%; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 10px; + height: 10px; + background: var(--primary-cyan); + border-radius: 50%; + box-shadow: 0 0 10px var(--primary-cyan); + } + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 50%; + background: conic-gradient(from 0deg, transparent 0%, transparent 60%, rgba(0, 240, 255, 0.4) 100%); + animation: radar-spin 2s linear infinite; + } + } + } + .card-overlay { position: absolute; bottom: 0; left: 0; width: 100%; - height: 50%; - background: linear-gradient(to top, rgba(0,0,0,0.8), transparent); + height: 60%; + background: linear-gradient(to top, rgba(0,0,0,0.9), transparent); } } &-body { - padding: 30px; + padding: 40px 32px; } &-header { display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 15px; + margin-bottom: 20px; .card-title { - font-size: 36px; - font-weight: bold; + font-size: 40px; // 加大标题 + font-weight: 700; color: #fff; flex: 1; margin-right: 20px; - line-height: 1.3; - text-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + line-height: 1.2; + text-shadow: 0 2px 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); + color: var(--primary-cyan); // 统一用青色或根据产品类型变化 + font-weight: 800; + text-shadow: 0 0 20px rgba(0, 240, 255, 0.3); } } &-desc { font-size: 26px; - color: #ccc; - line-height: 1.5; - margin-bottom: 25px; + color: var(--text-secondary); + line-height: 1.6; + margin-bottom: 32px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; @@ -188,52 +263,126 @@ .tags { display: flex; flex-wrap: wrap; - gap: 12px; - margin-bottom: 30px; + gap: 16px; + margin-bottom: 40px; .tag { - padding: 8px 18px; - border-radius: 12px; + padding: 10px 24px; + border-radius: 16px; font-size: 22px; font-weight: 500; + letter-spacing: 0.5px; &.cyan { - color: #00f0ff; - background: rgba(0, 240, 255, 0.1); - border: 1px solid rgba(0, 240, 255, 0.3); + color: var(--primary-cyan); + background: rgba(0, 240, 255, 0.08); + border: 1px solid rgba(0, 240, 255, 0.2); } &.blue { color: #3b82f6; - background: rgba(59, 130, 246, 0.1); - border: 1px solid rgba(59, 130, 246, 0.3); + background: rgba(59, 130, 246, 0.08); + border: 1px solid rgba(59, 130, 246, 0.2); } &.purple { - color: #a855f7; - background: rgba(168, 85, 247, 0.1); - border: 1px solid rgba(168, 85, 247, 0.3); + color: var(--primary-purple); + background: rgba(189, 0, 255, 0.08); + border: 1px solid rgba(189, 0, 255, 0.2); } } } &-footer { .btn-buy { - background: linear-gradient(90deg, #00b96b, #00f0ff); + background: linear-gradient(90deg, var(--primary-green), var(--primary-cyan)); color: #000; - font-weight: bold; + font-weight: 800; font-size: 30px; - border-radius: 50px; + border-radius: 60px; // 更圆润 border: none; - height: 80px; - line-height: 80px; - box-shadow: 0 5px 20px rgba(0, 185, 107, 0.3); + height: 90px; + line-height: 90px; + box-shadow: 0 10px 30px rgba(0, 185, 107, 0.25); + position: relative; + overflow: hidden; + // 流光效果 + &::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); + animation: shimmer 3s infinite; + } + &:active { - opacity: 0.9; + transform: scale(0.98); + box-shadow: 0 5px 15px rgba(0, 185, 107, 0.2); } } } } -.footer-spacer { - height: 100px; +@keyframes shimmer { + 0% { left: -100%; } + 20% { left: 100%; } + 100% { left: 100%; } +} + +@keyframes radar-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.footer-spacer { + height: 120px; +} + +// 骨架屏样式 +.skeleton-wrapper { + padding: 0 32px; + display: flex; + flex-direction: column; + gap: 48px; +} + +.skeleton-card { + height: 700px; + background: rgba(255, 255, 255, 0.02); + border-radius: 32px; + border: 1px solid rgba(255, 255, 255, 0.05); + overflow: hidden; + position: relative; + + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.03), transparent); + animation: skeleton-loading 1.5s infinite; + } +} + +@keyframes skeleton-loading { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +// 列表入场动画 +.fade-in-up { + animation: fadeInUp 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) forwards; + opacity: 0; + transform: translateY(40px); +} + +@keyframes fadeInUp { + to { + opacity: 1; + transform: translateY(0); + } } diff --git a/miniprogram/src/pages/index/index.tsx b/miniprogram/src/pages/index/index.tsx index d8c913a..337d332 100644 --- a/miniprogram/src/pages/index/index.tsx +++ b/miniprogram/src/pages/index/index.tsx @@ -65,8 +65,10 @@ export default function Index() { {loading ? ( - - 正在加载硬件配置... + + {[1, 2, 3].map(i => ( + + ))} ) : error ? ( @@ -75,14 +77,19 @@ export default function Index() { ) : ( - {products.map((item) => ( - goToDetail(item.id)}> + {products.map((item, index) => ( + goToDetail(item.id)} + > {item.static_image_url ? ( ) : ( - 🚀 + )} diff --git a/miniprogram/src/pages/order/checkout.scss b/miniprogram/src/pages/order/checkout.scss index 9fdc11c..ee1e99d 100644 --- a/miniprogram/src/pages/order/checkout.scss +++ b/miniprogram/src/pages/order/checkout.scss @@ -1,51 +1,154 @@ .page-container { min-height: 100vh; - background-color: #f7f8fa; - padding: 15px; - padding-bottom: 80px; + background-color: #050505; + color: #fff; + padding-bottom: 120px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } .section { - background: #fff; - border-radius: 12px; - padding: 20px; - margin-bottom: 15px; - box-shadow: 0 2px 8px rgba(0,0,0,0.02); + margin: 20px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 16px; + padding: 24px; + backdrop-filter: blur(10px); + position: relative; + + .section-title { + font-size: 28px; + font-weight: bold; + color: #fff; + margin-bottom: 20px; + display: block; + + &::before { + content: ''; + display: inline-block; + width: 6px; + height: 24px; + background: #00b96b; + margin-right: 12px; + vertical-align: middle; + border-radius: 3px; + } + } +} + +.delivery-type-section { + display: flex; + padding: 10px; + gap: 10px; + + .type-item { + flex: 1; + text-align: center; + padding: 16px 0; + font-size: 28px; + color: #888; + border-radius: 10px; + transition: all 0.3s; + + &.active { + background: #00b96b; + color: #fff; + font-weight: bold; + } + } } .address-section { - min-height: 80px; display: flex; - flex-direction: column; - justify-content: center; - - .row { - margin-bottom: 8px; - .name { font-size: 16px; font-weight: bold; margin-right: 10px; } - .phone { font-size: 14px; color: #666; } - } - .addr { font-size: 14px; color: #333; line-height: 1.4; } + align-items: center; + justify-content: space-between; - .placeholder-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; + .address-info { + flex: 1; + .user-info { + font-size: 30px; + font-weight: bold; + margin-bottom: 8px; + .phone { margin-left: 20px; color: #888; font-weight: normal; font-size: 26px; } + } + .address-text { + font-size: 26px; + color: #aaa; + line-height: 1.4; + } + .placeholder { + color: #00b96b; + font-size: 30px; + font-weight: bold; + } + } + + .arrow { + font-size: 30px; + color: #666; + margin-left: 20px; } - .placeholder { font-size: 16px; color: #00b96b; } } .product-section { - .p-name { font-size: 16px; font-weight: 500; margin-bottom: 10px; display: block; } - .row { display: flex; justify-content: space-between; align-items: center; } - .p-price { font-size: 16px; color: #333; } - .p-qty { font-size: 14px; color: #999; } - - .divider { height: 1px; background: #eee; margin: 15px 0; } - - .total-row { - .total-price { font-size: 20px; color: #ff4d4f; font-weight: bold; } - } + padding: 0; // Remove padding for list + overflow: hidden; + + .section-title { margin: 24px 24px 10px; } + + .product-item { + display: flex; + padding: 20px 24px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + + &:last-child { border-bottom: none; } + + .p-img { + width: 120px; + height: 120px; + border-radius: 8px; + background: #000; + margin-right: 20px; + object-fit: cover; + } + + .p-info { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + + .p-name { font-size: 28px; color: #fff; font-weight: bold; } + .p-desc { font-size: 24px; color: #888; } + + .p-meta { + display: flex; + justify-content: space-between; + align-items: center; + .p-price { font-size: 30px; color: #00b96b; font-weight: bold; } + .p-qty { font-size: 26px; color: #888; } + } + } + } +} + +.summary-section { + .row { + display: flex; + justify-content: space-between; + margin-bottom: 16px; + font-size: 28px; + color: #888; + + &.total { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: #fff; + font-weight: bold; + font-size: 32px; + .price { color: #00b96b; font-size: 40px; } + } + } } .bottom-bar { @@ -53,22 +156,36 @@ bottom: 0; left: 0; right: 0; - background: #fff; - padding: 10px 20px; - border-top: 1px solid #eee; + height: 110px; + background: rgba(20, 20, 20, 0.95); + backdrop-filter: blur(20px); + border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: flex-end; + padding: 0 30px; + z-index: 100; + + .total-label { font-size: 28px; color: #fff; margin-right: 20px; } + .total-price { font-size: 40px; color: #00b96b; font-weight: bold; margin-right: 30px; } .btn-submit { - background: #00b96b; - color: #fff; - border-radius: 22px; + background: linear-gradient(135deg, #00b96b 0%, #00f0ff 100%); + color: #000; + border-radius: 40px; + padding: 0 60px; + height: 80px; + line-height: 80px; + font-size: 32px; + font-weight: bold; border: none; - font-size: 16px; - height: 44px; - line-height: 44px; + box-shadow: 0 0 20px rgba(0, 185, 107, 0.3); + + &:active { transform: scale(0.98); } + &.disabled { + background: #333; + color: #666; + box-shadow: none; + } } } - -.safe-area-bottom { - padding-bottom: constant(safe-area-inset-bottom); - padding-bottom: env(safe-area-inset-bottom); -} diff --git a/miniprogram/src/pages/order/checkout.tsx b/miniprogram/src/pages/order/checkout.tsx index 4d14116..34f4aa6 100644 --- a/miniprogram/src/pages/order/checkout.tsx +++ b/miniprogram/src/pages/order/checkout.tsx @@ -1,98 +1,220 @@ -import { View, Text, Button } from '@tarojs/components' +import { View, Text, Image, ScrollView, Button } from '@tarojs/components' import Taro, { useRouter, useLoad } from '@tarojs/taro' -import { useState } from 'react' +import { useState, useMemo } from 'react' import { getConfigDetail, createOrder } from '../../api' +import { getSelectedItems, removeItem } from '../../utils/cart' import './checkout.scss' export default function Checkout() { const router = useRouter() - const { id, quantity } = router.params - const [product, setProduct] = useState(null) + const params = router.params + const [items, setItems] = useState([]) const [address, setAddress] = useState(null) - const [contact, setContact] = useState({ name: '', phone: '' }) + const [deliveryType, setDeliveryType] = useState<'delivery' | 'pickup'>('delivery') + const [userAddress, setUserAddress] = useState(null) + const [loading, setLoading] = useState(true) + + const PICKUP_ADDRESS = { + userName: '云南量迹科技有限公司', + telNumber: '18585164448', + provinceName: '云南省', + cityName: '昆明市', + countyName: '西山区', + detailInfo: '永昌街道办事处云纺国际商厦 B 座 1406 号' + } useLoad(async () => { - if (id) { - const res = await getConfigDetail(Number(id)) - setProduct(res) + if (params.from === 'cart') { + const cartItems = getSelectedItems() + if (cartItems.length === 0) { + Taro.navigateBack() + return + } + setItems(cartItems) + setLoading(false) + } else if (params.id) { + try { + const res = await getConfigDetail(params.id) + setItems([{ + id: res.id, + name: res.name, + price: res.price, + image: res.static_image_url || res.detail_image_url, + quantity: Number(params.quantity) || 1, + description: res.description + }]) + } catch (err) { + console.error(err) + Taro.showToast({ title: '商品加载失败', icon: 'none' }) + } finally { + setLoading(false) + } } }) const chooseAddress = async () => { + if (deliveryType === 'pickup') return try { - const res = await Taro.chooseAddress() - setAddress(res) - setContact({ name: res.userName, phone: res.telNumber }) - } catch (e) { - Taro.showToast({ title: '需要授权获取地址', icon: 'none' }) + const res = await Taro.chooseAddress() + setAddress(res) + setUserAddress(res) + } catch (err) { + console.error(err) + // User cancelled or auth denied } } + const handleTypeChange = (type: 'delivery' | 'pickup') => { + if (type === deliveryType) return + setDeliveryType(type) + if (type === 'pickup') { + setAddress(PICKUP_ADDRESS) + } else { + setAddress(userAddress) + } + } + + const totalPrice = useMemo(() => { + return items.reduce((sum, item) => sum + item.price * item.quantity, 0) + }, [items]) + const submitOrder = async () => { if (!address) { - Taro.showToast({ title: '请选择收货地址', icon: 'none' }) - return + Taro.showToast({ title: '请选择收货地址', icon: 'none' }) + return } - + + Taro.showLoading({ title: '提交中...' }) + try { - Taro.showLoading({ title: '正在下单...' }) + const orderPromises = items.map(item => { const orderData = { - goodid: product.id, - quantity: Number(quantity || 1), - customer_name: contact.name, - phone_number: contact.phone, - shipping_address: `${address.provinceName}${address.cityName}${address.countyName}${address.detailInfo}`, - // ref_code: Taro.getStorageSync('ref_code') - } - - const res = await createOrder(orderData) - Taro.hideLoading() - - if (res.order_id) { - Taro.redirectTo({ url: `/pages/order/payment?id=${res.order_id}` }) + goodid: item.id, + quantity: item.quantity, + customer_name: address.userName, + phone_number: address.telNumber, + shipping_address: `${address.provinceName}${address.cityName}${address.countyName}${address.detailInfo}` } + return createOrder(orderData) + }) + + const results = await Promise.all(orderPromises) + + // If from cart, remove bought items + if (params.from === 'cart') { + items.forEach(item => removeItem(item.id)) + } + + Taro.hideLoading() + + if (results.length === 1) { + // Single order, go to payment + const orderId = results[0].order_id + Taro.redirectTo({ + url: `/pages/order/payment?id=${orderId}` + }) + } else { + // Multiple orders + Taro.showModal({ + title: '下单成功', + content: `成功创建 ${results.length} 个订单,请前往订单列表支付`, + showCancel: false, + confirmText: '去支付', + success: () => { + Taro.redirectTo({ url: '/pages/order/list' }) + } + }) + } + } catch (err) { - Taro.hideLoading() - console.error(err) + Taro.hideLoading() + console.error(err) + Taro.showToast({ title: '下单失败', icon: 'none' }) } } - if (!product) return Loading... + if (loading) return Loading... return ( - + + + {/* Delivery Type Section */} + + handleTypeChange('delivery')} + > + 快递配送 + + handleTypeChange('pickup')} + > + 门店自提 + + + + {/* Address Section */} {address ? ( - - - {contact.name} - {contact.phone} + + + {address.userName} + {address.telNumber} + + + {address.provinceName}{address.cityName}{address.countyName}{address.detailInfo} - {address.provinceName}{address.cityName}{address.countyName}{address.detailInfo} ) : ( - - + 选择收货地址 + + + 添加收货地址 )} + {deliveryType === 'delivery' && } + {/* Products Section */} - {product.name} - - ¥{product.price} - x {quantity} - - - - 合计 - ¥{(product.price * (Number(quantity) || 1)).toFixed(2)} - + 商品信息 + {items.map((item, idx) => ( + + + + {item.name} + {item.description} + + ¥{item.price} + x{item.quantity} + + + + ))} - - + {/* Summary Section */} + + + 商品总价 + ¥{totalPrice} + + + 运费 + ¥0 + + + 合计 + ¥{totalPrice} + - + + + {/* Bottom Bar */} + + 共{items.length}件 + ¥{totalPrice} + + + ) } diff --git a/miniprogram/src/pages/services/index.scss b/miniprogram/src/pages/services/index.scss index 25f8476..d3d5fe6 100644 --- a/miniprogram/src/pages/services/index.scss +++ b/miniprogram/src/pages/services/index.scss @@ -128,59 +128,158 @@ .process-section { margin-top: 60px; padding: 40px 20px; - background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,185,107,0.05) 100%); - border-radius: 30px; - border: 1px solid rgba(255,255,255,0.05); + position: relative; + overflow: hidden; + // Background Tech Grid + &::before { + content: ''; + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + background-image: + linear-gradient(rgba(0, 185, 107, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 185, 107, 0.03) 1px, transparent 1px); + background-size: 20px 20px; + z-index: 0; + } + .section-title { color: #fff; text-align: center; font-size: 36px; font-weight: bold; - margin-bottom: 40px; + margin-bottom: 60px; display: block; - text-shadow: 0 0 10px rgba(0, 185, 107, 0.5); + text-shadow: 0 0 15px rgba(0, 185, 107, 0.8); + position: relative; + z-index: 1; + letter-spacing: 2px; + + &::after { + content: ''; + display: block; + width: 60px; + height: 4px; + background: #00b96b; + margin: 15px auto 0; + border-radius: 2px; + box-shadow: 0 0 10px #00b96b; + } } .process-steps { display: flex; - flex-wrap: wrap; - justify-content: space-between; - gap: 20px; + flex-direction: column; + position: relative; + z-index: 1; + padding: 0 20px; + + // Vertical connecting line + &::before { + content: ''; + position: absolute; + top: 20px; + bottom: 20px; + left: 60px; // Center of the icon (40px + padding) + width: 2px; + background: rgba(255, 255, 255, 0.1); + z-index: 0; + } + + // Moving signal on the line + &::after { + content: ''; + position: absolute; + top: 20px; + left: 60px; + width: 2px; + height: 100px; + background: linear-gradient(to bottom, transparent, #00b96b, transparent); + animation: signalFlow 3s infinite linear; + z-index: 0; + } } .step-item { - width: 48%; // 2 columns display: flex; - flex-direction: column; align-items: center; - margin-bottom: 30px; + margin-bottom: 40px; + position: relative; + + &:last-child { margin-bottom: 0; } .step-icon { width: 80px; height: 80px; - border-radius: 24px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(0, 185, 107, 0.3); + border-radius: 20px; + background: rgba(0, 0, 0, 0.6); + border: 1px solid rgba(0, 185, 107, 0.5); display: flex; align-items: center; justify-content: center; - margin-bottom: 15px; color: #00b96b; - font-size: 32px; + font-size: 36px; font-weight: bold; + margin-right: 30px; + position: relative; + z-index: 1; + box-shadow: 0 0 15px rgba(0, 185, 107, 0.2); + transition: all 0.3s ease; + + // Pulse effect for icon + &::before { + content: ''; + position: absolute; + top: -5px; bottom: -5px; left: -5px; right: -5px; + border-radius: 24px; + border: 1px solid rgba(0, 185, 107, 0.3); + animation: pulseBorder 2s infinite; + } } - .step-title { - color: #fff; - font-size: 28px; - font-weight: bold; - margin-bottom: 5px; - } - - .step-desc { - color: #666; - font-size: 24px; + // Content Card + .step-content-wrapper { + flex: 1; + background: linear-gradient(90deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); + border: 1px solid rgba(255, 255, 255, 0.05); + border-left: 4px solid #00b96b; + padding: 20px 24px; + border-radius: 0 16px 16px 0; + backdrop-filter: blur(5px); + transform: translateX(0); + transition: all 0.3s ease; + + &:active { + background: rgba(255, 255, 255, 0.05); + transform: translateX(5px); + } + + .step-title { + color: #fff; + font-size: 30px; + font-weight: bold; + margin-bottom: 8px; + display: block; + text-shadow: 0 0 5px rgba(0,0,0,0.5); + } + + .step-desc { + color: #888; + font-size: 24px; + line-height: 1.4; + } } } } + +@keyframes signalFlow { + 0% { top: 0; opacity: 0; } + 20% { opacity: 1; } + 80% { opacity: 1; } + 100% { top: 100%; opacity: 0; } +} + +@keyframes pulseBorder { + 0% { transform: scale(1); opacity: 0.5; } + 100% { transform: scale(1.15); opacity: 0; } +} diff --git a/miniprogram/src/pages/services/index.tsx b/miniprogram/src/pages/services/index.tsx index fec453c..5711a30 100644 --- a/miniprogram/src/pages/services/index.tsx +++ b/miniprogram/src/pages/services/index.tsx @@ -91,8 +91,10 @@ export default function ServicesIndex() { ].map((step) => ( {step.id} - {step.title} - {step.desc} + + {step.title} + {step.desc} + ))} diff --git a/miniprogram/src/pages/user/index.scss b/miniprogram/src/pages/user/index.scss index fbd1d2e..56fc6fb 100644 --- a/miniprogram/src/pages/user/index.scss +++ b/miniprogram/src/pages/user/index.scss @@ -1,53 +1,207 @@ .page-container { min-height: 100vh; - background-color: #f7f8fa; + background-color: #050505; + color: #fff; + padding: 30px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } -.header { - background: #fff; - padding: 40px 20px; +@keyframes pulse { + 0% { box-shadow: 0 0 0 0 rgba(0, 185, 107, 0.4); } + 70% { box-shadow: 0 0 0 10px rgba(0, 185, 107, 0); } + 100% { box-shadow: 0 0 0 0 rgba(0, 185, 107, 0); } +} + +@keyframes float { + 0% { transform: translateY(0); } + 50% { transform: translateY(-5px); } + 100% { transform: translateY(0); } +} + +.profile-card { + background: linear-gradient(135deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.02) 100%); + border: 1px solid rgba(255,255,255,0.05); + backdrop-filter: blur(20px); + border-radius: 20px; + padding: 40px; display: flex; align-items: center; - margin-bottom: 20px; + margin-bottom: 30px; + position: relative; + overflow: hidden; - .avatar { - width: 60px; - height: 60px; - border-radius: 30px; - margin-right: 15px; - background: #eee; + .card-bg-effect { + position: absolute; + top: -50%; + right: -20%; + width: 200px; + height: 200px; + background: radial-gradient(circle, rgba(0, 185, 107, 0.2) 0%, transparent 70%); + filter: blur(40px); + z-index: 0; } - - .nickname { - font-size: 18px; - font-weight: bold; - color: #333; + + .avatar-container { + position: relative; + margin-right: 30px; + z-index: 1; + + .avatar { + width: 120px; + height: 120px; + border-radius: 60px; + border: 2px solid rgba(0, 185, 107, 0.5); + background: #000; + } + + .online-dot { + position: absolute; + bottom: 5px; + right: 5px; + width: 24px; + height: 24px; + background: #00b96b; + border-radius: 50%; + border: 3px solid #111; + animation: pulse 2s infinite; + } + } + + .info-col { + flex: 1; + z-index: 1; + display: flex; + flex-direction: column; + + .nickname { + font-size: 36px; + font-weight: bold; + color: #fff; + margin-bottom: 8px; + text-shadow: 0 0 10px rgba(0,0,0,0.5); + } + + .uid { + font-size: 24px; + color: #666; + margin-bottom: 20px; + font-family: monospace; + } + + .btn-login { + background: rgba(0, 185, 107, 0.2); + border: 1px solid #00b96b; + color: #00b96b; + font-size: 24px; + border-radius: 30px; + padding: 0 30px; + height: 60px; + line-height: 58px; + margin: 0; + width: fit-content; + + &:active { background: rgba(0, 185, 107, 0.3); } + } } } -.menu { - background: #fff; - - .item { - padding: 15px 20px; - border-bottom: 1px solid #eee; +.stats-row { display: flex; justify-content: space-between; - align-items: center; - font-size: 16px; - position: relative; + margin-bottom: 30px; + padding: 0 10px; - &:last-child { border-bottom: none; } - - .arrow { color: #ccc; } - - .btn-contact { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0; + .stat-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + + .stat-val { font-size: 36px; font-weight: bold; color: #fff; margin-bottom: 5px; } + .stat-lbl { font-size: 24px; color: #666; } + } +} + +.service-container { + padding-bottom: 40px; + + .service-group { + margin-bottom: 40px; + + .group-title { + display: block; + font-size: 32px; + font-weight: bold; + color: #fff; + margin-bottom: 20px; + padding-left: 10px; + border-left: 4px solid #00b96b; + line-height: 1; + } + + .grid-layout { + display: flex; + flex-wrap: wrap; + gap: 20px; + + .grid-item { + width: calc(33.33% - 14px); // 3 items per row, accounting for gap + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 20px; + padding: 30px 10px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; + box-sizing: border-box; + backdrop-filter: blur(10px); + transition: all 0.2s ease; + + &:active { + background: rgba(255, 255, 255, 0.08); + transform: scale(0.95); + } + + .icon-box { + width: 80px; + height: 80px; + border-radius: 50%; + background: rgba(0, 185, 107, 0.1); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 16px; + + .icon { font-size: 40px; } + } + + .item-title { + font-size: 26px; + color: #ddd; + text-align: center; + } + + .contact-overlay { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + opacity: 0; + } + } + } + } +} + +.version-info { + margin-top: 60px; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + + text { + font-size: 20px; + color: #333; } - } } diff --git a/miniprogram/src/pages/user/index.tsx b/miniprogram/src/pages/user/index.tsx index a4b6cd5..76c3ebe 100644 --- a/miniprogram/src/pages/user/index.tsx +++ b/miniprogram/src/pages/user/index.tsx @@ -13,33 +13,99 @@ export default function UserIndex() { const goOrders = () => Taro.navigateTo({ url: '/pages/order/list' }) const goDistributor = () => Taro.navigateTo({ url: '/subpackages/distributor/index' }) + const goInvite = () => Taro.navigateTo({ url: '/subpackages/distributor/invite' }) + const goWithdraw = () => Taro.navigateTo({ url: '/subpackages/distributor/withdraw' }) + + const handleAddress = async () => { + try { await Taro.chooseAddress() } catch(e) {} + } + const login = () => { - // Trigger login again if needed Taro.reLaunch({ url: '/pages/index/index' }) } + const serviceGroups = [ + { + title: '基础服务', + items: [ + { title: '我的订单', icon: '📦', action: goOrders }, + { title: '地址管理', icon: '📍', action: handleAddress }, + { title: '新增地址', icon: '📝', action: handleAddress }, + ] + }, + { + title: '分销中心', + items: [ + { title: '分销首页', icon: '⚡', action: goDistributor }, + { title: '推广邀请', icon: '🤝', action: goInvite }, + { title: '佣金提现', icon: '💰', action: goWithdraw }, + ] + }, + { + title: '其他', + items: [ + { title: '联系客服', icon: '🎧', isContact: true } + ] + } + ] + + const stats = [ + { label: '余额', value: '0.00' }, + { label: '积分', value: '0' }, + { label: '优惠券', value: '0' } + ] + return ( - - - {userInfo?.nickname || '未登录'} - {!userInfo && } + {/* Profile Card */} + + + + {userInfo && } + + + {userInfo?.nickname || '未登录用户'} + ID: {userInfo ? '888888' : '----'} + {!userInfo && ( + + )} + + - - - 我的订单 - > - - - 分销中心 - > - - - 联系客服 -