new
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-12 12:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('community', '0007_announcement'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='activity',
|
||||
name='signup_form_config',
|
||||
field=models.JSONField(blank=True, default=list, help_text='配置报名时需要收集的信息,JSON格式,例如:[{"name": "phone", "label": "手机号", "type": "text", "required": true}]', verbose_name='报名表单配置'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activitysignup',
|
||||
name='signup_info',
|
||||
field=models.JSONField(blank=True, default=dict, verbose_name='报名信息'),
|
||||
),
|
||||
]
|
||||
@@ -14,6 +14,12 @@ class Activity(models.Model):
|
||||
location = models.CharField(max_length=100, verbose_name="活动地点")
|
||||
max_participants = models.IntegerField(default=50, verbose_name="最大报名人数")
|
||||
is_active = models.BooleanField(default=True, verbose_name="是否启用")
|
||||
signup_form_config = models.JSONField(
|
||||
default=list,
|
||||
verbose_name="报名表单配置",
|
||||
blank=True,
|
||||
help_text='配置报名时需要收集的信息,JSON格式,例如:[{"name": "phone", "label": "手机号", "type": "text", "required": true}]'
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||
|
||||
def clean(self):
|
||||
@@ -55,6 +61,11 @@ class ActivitySignup(models.Model):
|
||||
activity = models.ForeignKey(Activity, on_delete=models.CASCADE, related_name='signups', verbose_name="活动")
|
||||
user = models.ForeignKey(WeChatUser, on_delete=models.CASCADE, related_name='activity_signups', verbose_name="报名用户")
|
||||
signup_time = models.DateTimeField(auto_now_add=True, verbose_name="报名时间")
|
||||
signup_info = models.JSONField(
|
||||
default=dict,
|
||||
verbose_name="报名信息",
|
||||
blank=True
|
||||
)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='confirmed', verbose_name="状态")
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -10,10 +10,12 @@ class ActivitySerializer(serializers.ModelSerializer):
|
||||
fields = '__all__'
|
||||
|
||||
class ActivitySignupSerializer(serializers.ModelSerializer):
|
||||
activity_info = ActivitySerializer(source='activity', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ActivitySignup
|
||||
fields = ['id', 'activity', 'user', 'signup_time', 'status']
|
||||
read_only_fields = ['signup_time', 'status']
|
||||
fields = ['id', 'activity', 'activity_info', 'user', 'signup_time', 'status', 'signup_info']
|
||||
read_only_fields = ['signup_time', 'status', 'user']
|
||||
|
||||
class TopicMediaSerializer(serializers.ModelSerializer):
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
@@ -37,7 +37,25 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
if activity.signups.count() >= activity.max_participants:
|
||||
return Response({'error': '活动名额已满'}, status=400)
|
||||
|
||||
signup = ActivitySignup.objects.create(activity=activity, user=user)
|
||||
# Get signup info
|
||||
signup_info = request.data.get('signup_info', {})
|
||||
|
||||
# Basic validation
|
||||
if activity.signup_form_config:
|
||||
required_fields = [f['name'] for f in activity.signup_form_config if f.get('required')]
|
||||
for field in required_fields:
|
||||
# Simple check: field exists and is not empty string (if it's a string)
|
||||
val = signup_info.get(field)
|
||||
if val is None or (isinstance(val, str) and not val.strip()):
|
||||
# Try to find label for better error message
|
||||
label = next((f['label'] for f in activity.signup_form_config if f['name'] == field), field)
|
||||
return Response({'error': f'请填写: {label}'}, status=400)
|
||||
|
||||
signup = ActivitySignup.objects.create(
|
||||
activity=activity,
|
||||
user=user,
|
||||
signup_info=signup_info
|
||||
)
|
||||
serializer = ActivitySignupSerializer(signup)
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
backend/tmp03_zfbds
Normal file
1
backend/tmp03_zfbds
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmp1_d4jq1k
Normal file
1
backend/tmp1_d4jq1k
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmp1vnn2xz8
Normal file
1
backend/tmp1vnn2xz8
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmp24154cux
Normal file
BIN
backend/tmp24154cux
Normal file
Binary file not shown.
BIN
backend/tmp3tj2qz45
Normal file
BIN
backend/tmp3tj2qz45
Normal file
Binary file not shown.
BIN
backend/tmp4ckc8bd0
Normal file
BIN
backend/tmp4ckc8bd0
Normal file
Binary file not shown.
1
backend/tmp4gx7ct_g
Normal file
1
backend/tmp4gx7ct_g
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmp51ic78xj
Normal file
BIN
backend/tmp51ic78xj
Normal file
Binary file not shown.
1
backend/tmp5_bb2xac
Normal file
1
backend/tmp5_bb2xac
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmp5ji9l72c
Normal file
BIN
backend/tmp5ji9l72c
Normal file
Binary file not shown.
BIN
backend/tmp899rmzxr
Normal file
BIN
backend/tmp899rmzxr
Normal file
Binary file not shown.
1
backend/tmp8q9poex7
Normal file
1
backend/tmp8q9poex7
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmp9v8b6en2
Normal file
1
backend/tmp9v8b6en2
Normal file
@@ -0,0 +1 @@
|
||||
{"files": [{"filename": "inflection-0.1.1.tar.gz", "hashes": {"sha256": "70a0014cb3b36f6c8230756ae5c1bdc6aecd1f5beff240d8207f31ba9c13948c"}, "requires-python": null, "size": 7586, "upload-time": "2012-02-24T20:09:17.268105Z", "url": "../../packages/04/68/55d46b34f7d50f96c3bb26582c7334bdeae422c9ac0058ed48d79f7c5ade/inflection-0.1.1.tar.gz", "yanked": false}, {"filename": "inflection-0.1.2.tar.gz", "hashes": {"sha256": "e310c98c69ffac3866cc38b0d869a4b94aa61a59f890102e341d4c8c96d4d228"}, "requires-python": null, "size": 8543, "upload-time": "2012-03-13T21:45:11.124181Z", "url": "../../packages/fb/d8/fb45d4ba177fef3bd87f103edc2b28fcdf24f43952928d47cef73ec6d3f4/inflection-0.1.2.tar.gz", "yanked": false}, {"filename": "inflection-0.2.0.tar.gz", "hashes": {"sha256": "dab6ca89b6a91aecc8ba28ad9376b85a362c413733d108a3ffaae2e0b768e57e"}, "requires-python": null, "size": 8153, "upload-time": "2013-06-15T14:55:27.831074Z", "url": "../../packages/17/17/e063022e2b77cb9f4a83ed4089e5b0debdddbc6b685c679f387b657c73c1/inflection-0.2.0.tar.gz", "yanked": false}, {"filename": "inflection-0.2.1.tar.gz", "hashes": {"sha256": "96e6233bb5b9dcf0a34081bd31b1ca7ea2aea9c7110615acc14fdcaa3e71071f"}, "requires-python": null, "size": 8219, "upload-time": "2014-09-03T14:37:27.192869Z", "url": "../../packages/d4/d5/5ae57400b6c5374fe279c76e220588a554688138945601f5dde4733e22be/inflection-0.2.1.tar.gz", "yanked": false}, {"filename": "inflection-0.3.0.tar.gz", "hashes": {"sha256": "c6ab173181fd3bbee53187b7535f395ed802ef69d9f3bb5f8c2a5cdfb3e92d08"}, "requires-python": null, "size": 8565, "upload-time": "2015-03-01T17:36:33.912921Z", "url": "../../packages/5a/42/489536aac4f16828e19823406e83b943860d30767c71f390511a45ce095d/inflection-0.3.0.tar.gz", "yanked": false}, {"filename": "inflection-0.3.1.tar.gz", "hashes": {"sha256": "18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca"}, "requires-python": null, "size": 8715, "upload-time": "2015-05-03T08:08:12.824777Z", "url": "../../packages/d5/35/a6eb45b4e2356fe688b21570864d4aa0d0a880ce387defe9c589112077f8/inflection-0.3.1.tar.gz", "yanked": false}, {"filename": "inflection-0.4.0-py2.py3-none-any.whl", "hashes": {"sha256": "9a15d3598f01220e93f2207c432cfede50daff53137ce660fb8be838ef1ca6cc"}, "requires-python": ">=3.5", "size": 5789, "upload-time": "2020-04-06T19:35:36.732518Z", "url": "../../packages/52/c1/36be286d85dbd76527fb613527222a795d7c071da195fa916e7bf3cb03cb/inflection-0.4.0-py2.py3-none-any.whl", "yanked": false}, {"filename": "inflection-0.4.0.tar.gz", "hashes": {"sha256": "32a5c3341d9583ec319548b9015b7fbdf8c429cbcb575d326c33ae3a0e90d52c"}, "requires-python": ">=3.5", "size": 14536, "upload-time": "2020-04-06T19:35:37.724962Z", "url": "../../packages/e8/dc/3986343faf9631d8bc61d8a6a1331b5f4f08723dbce3b39f524c367a1621/inflection-0.4.0.tar.gz", "yanked": false}, {"filename": "inflection-0.5.0-py2.py3-none-any.whl", "hashes": {"sha256": "88b101b2668a1d81d6d72d4c2018e53bc6c7fc544c987849da1c7f77545c3bc9"}, "requires-python": ">=3.5", "size": 5840, "upload-time": "2020-06-06T08:04:42.855673Z", "url": "../../packages/d2/cd/f04c661d1b5ba6b7d77008e8fedd28a27a683eeeffcd93c6b2dbe54ea983/inflection-0.5.0-py2.py3-none-any.whl", "yanked": false}, {"filename": "inflection-0.5.0.tar.gz", "hashes": {"sha256": "f576e85132d34f5bf7df5183c2c6f94cfb32e528f53065345cf71329ba0b8924"}, "requires-python": ">=3.5", "size": 14830, "upload-time": "2020-06-06T08:04:44.356715Z", "url": "../../packages/87/41/07fa466e0525635243f0cb94f81b9e99508edc23af7d9bf6c2009e11eadc/inflection-0.5.0.tar.gz", "yanked": false}, {"filename": "inflection-0.5.1-py2.py3-none-any.whl", "hashes": {"sha256": "f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, "requires-python": ">=3.5", "size": 9454, "upload-time": "2020-08-22T08:16:27.816929Z", "url": "../../packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", "yanked": false}, {"filename": "inflection-0.5.1.tar.gz", "hashes": {"sha256": "1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, "requires-python": ">=3.5", "size": 15091, "upload-time": "2020-08-22T08:16:29.139062Z", "url": "../../packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", "yanked": false}], "meta": {"api-version": "1.1", "_last-serial": "8016752"}, "name": "inflection", "versions": ["0.1.1", "0.1.2", "0.2.0", "0.2.1", "0.3.0", "0.3.1", "0.4.0", "0.5.0", "0.5.1"]}
|
||||
1
backend/tmp_7r3u7cx
Normal file
1
backend/tmp_7r3u7cx
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmp_8j_ohti
Normal file
BIN
backend/tmp_8j_ohti
Normal file
Binary file not shown.
BIN
backend/tmp_s882ns6
Normal file
BIN
backend/tmp_s882ns6
Normal file
Binary file not shown.
BIN
backend/tmpci9az1ug
Normal file
BIN
backend/tmpci9az1ug
Normal file
Binary file not shown.
1
backend/tmpclevfosk
Normal file
1
backend/tmpclevfosk
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmpd7_t3u5l
Normal file
BIN
backend/tmpd7_t3u5l
Normal file
Binary file not shown.
BIN
backend/tmpe7h8t1mx
Normal file
BIN
backend/tmpe7h8t1mx
Normal file
Binary file not shown.
BIN
backend/tmpff33bgsu
Normal file
BIN
backend/tmpff33bgsu
Normal file
Binary file not shown.
1
backend/tmpg0cf2uch
Normal file
1
backend/tmpg0cf2uch
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmpg5yf1vug
Normal file
1
backend/tmpg5yf1vug
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmpgdqeal4u
Normal file
BIN
backend/tmpgdqeal4u
Normal file
Binary file not shown.
BIN
backend/tmpgwrshcaf
Normal file
BIN
backend/tmpgwrshcaf
Normal file
Binary file not shown.
1
backend/tmpja1tb9xu
Normal file
1
backend/tmpja1tb9xu
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmpk9t6y5ru
Normal file
BIN
backend/tmpk9t6y5ru
Normal file
Binary file not shown.
1
backend/tmplk0xax0p
Normal file
1
backend/tmplk0xax0p
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmpmmzl1px4
Normal file
1
backend/tmpmmzl1px4
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmpndp11ryk
Normal file
1
backend/tmpndp11ryk
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmpnejw_r50
Normal file
BIN
backend/tmpnejw_r50
Normal file
Binary file not shown.
1
backend/tmpnzxvnw_m
Normal file
1
backend/tmpnzxvnw_m
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmpq0dkxu9m
Normal file
BIN
backend/tmpq0dkxu9m
Normal file
Binary file not shown.
1
backend/tmpqtdz0k95
Normal file
1
backend/tmpqtdz0k95
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmprmy_tx9e
Normal file
BIN
backend/tmprmy_tx9e
Normal file
Binary file not shown.
1
backend/tmprrcxhd6f
Normal file
1
backend/tmprrcxhd6f
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmptpegm9jm
Normal file
1
backend/tmptpegm9jm
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmptqteu95n
Normal file
BIN
backend/tmptqteu95n
Normal file
Binary file not shown.
1
backend/tmpx7xnz5we
Normal file
1
backend/tmpx7xnz5we
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmpx8gmdsbe
Normal file
1
backend/tmpx8gmdsbe
Normal file
File diff suppressed because one or more lines are too long
1
backend/tmpxkfqsuyp
Normal file
1
backend/tmpxkfqsuyp
Normal file
File diff suppressed because one or more lines are too long
BIN
backend/tmpyphiepes
Normal file
BIN
backend/tmpyphiepes
Normal file
Binary file not shown.
BIN
backend/tmpz0f610a7
Normal file
BIN
backend/tmpz0f610a7
Normal file
Binary file not shown.
@@ -42,7 +42,7 @@
|
||||
"@tarojs/runtime": "3.6.20",
|
||||
"@tarojs/shared": "3.6.20",
|
||||
"@tarojs/taro": "3.6.20",
|
||||
"marked": "^17.0.2",
|
||||
"marked": "^4.3.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"taro-ui": "^3.0.0-alpha.10"
|
||||
|
||||
@@ -48,12 +48,12 @@ export const getAnnouncements = () => request({ url: '/community/announcements/'
|
||||
// Activities
|
||||
export const getActivities = () => request({ url: '/community/activities/' })
|
||||
export const getActivityDetail = (id: number) => request({ url: `/community/activities/${id}/` })
|
||||
export const signupActivity = (id: number) => request({ url: `/community/activities/${id}/signup/`, method: 'POST' })
|
||||
export const signupActivity = (id: number, data?: any) => request({ url: `/community/activities/${id}/signup/`, method: 'POST', data })
|
||||
export const getMySignups = () => request({ url: '/community/activities/my_signups/' })
|
||||
|
||||
// Upload Media for Forum
|
||||
export const uploadMedia = (filePath: string, type: 'image' | 'video') => {
|
||||
const BASE_URL = process.env.TARO_APP_API_URL || 'https://market.quant-speed.com/api'
|
||||
const BASE_URL = (typeof process !== 'undefined' && process.env && process.env.TARO_APP_API_URL) || 'https://market.quant-speed.com/api'
|
||||
return Taro.uploadFile({
|
||||
url: `${BASE_URL}/community/media/`,
|
||||
filePath,
|
||||
|
||||
@@ -1,77 +1,142 @@
|
||||
.forum-page {
|
||||
min-height: 100vh;
|
||||
background-color: #000;
|
||||
padding-bottom: 40px;
|
||||
background-color: #121212; /* Darker background for modern feel */
|
||||
padding-bottom: 80px;
|
||||
color: #fff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
|
||||
/* Global Animations */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); box-shadow: 0 4px 12px rgba(0, 185, 107, 0.4); }
|
||||
50% { transform: scale(1.05); box-shadow: 0 8px 24px rgba(0, 185, 107, 0.6); }
|
||||
100% { transform: scale(1); box-shadow: 0 4px 12px rgba(0, 185, 107, 0.4); }
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
padding: 40px 20px 20px;
|
||||
padding: 60px 20px 30px;
|
||||
text-align: center;
|
||||
background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,185,107,0.1) 100%);
|
||||
background: linear-gradient(180deg, rgba(0,0,0,0.8) 0%, rgba(0,185,107,0.15) 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
/* Subtle pattern overlay if desired */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-image: radial-gradient(#333 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
opacity: 0.1;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
font-size: 36px; /* Increased from 32px */
|
||||
font-weight: 800;
|
||||
margin-bottom: 12px;
|
||||
color: #fff;
|
||||
letter-spacing: -0.5px;
|
||||
text-shadow: 0 2px 10px rgba(0,0,0,0.5);
|
||||
z-index: 1;
|
||||
|
||||
.highlight {
|
||||
color: #00b96b;
|
||||
background: linear-gradient(45deg, #00b96b, #00ff9d);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
color: #aaa;
|
||||
font-size: 17px; /* Increased from 16px */
|
||||
margin-bottom: 30px;
|
||||
font-weight: 500;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
z-index: 2;
|
||||
|
||||
.at-search-bar {
|
||||
flex: 1;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
|
||||
&::after {
|
||||
border-bottom: none;
|
||||
}
|
||||
&::after { border-bottom: none; }
|
||||
|
||||
.at-search-bar__input-cnt {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid #333;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 26px; /* More rounded */
|
||||
transition: all 0.3s ease;
|
||||
height: 48px; /* Taller touch target (from 44px) */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:focus-within {
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
border-color: #00b96b;
|
||||
box-shadow: 0 0 0 2px rgba(0, 185, 107, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.at-search-bar__input {
|
||||
color: #fff;
|
||||
font-size: 17px; /* Larger input text (from 16px) */
|
||||
}
|
||||
|
||||
.at-search-bar__placeholder {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.create-btn {
|
||||
background-color: #00b96b;
|
||||
background: linear-gradient(135deg, #00b96b 0%, #009456 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 26px;
|
||||
padding: 0 24px;
|
||||
font-size: 16px; /* Larger button text */
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 12px rgba(0, 185, 107, 0.3);
|
||||
transition: transform 0.2s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-container {
|
||||
margin: 0 10px 15px;
|
||||
background: #141414; /* Darker card background */
|
||||
border-radius: 12px;
|
||||
margin: 0 16px 20px;
|
||||
background: #1e1e1e; /* Card background */
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||
animation: fadeInUp 0.6s ease-out forwards;
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
@@ -83,8 +148,9 @@
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,13 +161,14 @@
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 0 12px;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid #ff4d4f;
|
||||
|
||||
.item-text {
|
||||
font-size: 14px;
|
||||
color: #ccc;
|
||||
color: #ddd;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -112,17 +179,23 @@
|
||||
.star-users-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
padding-bottom: 5px; /* Scrollbar space if visible */
|
||||
|
||||
.star-user-card {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
margin-right: 16px;
|
||||
width: 80px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 12px 8px;
|
||||
border-radius: 8px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
transition: transform 0.2s;
|
||||
|
||||
&:active {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 48px;
|
||||
@@ -130,12 +203,13 @@
|
||||
border-radius: 50%;
|
||||
border: 2px solid #ffd700;
|
||||
margin-bottom: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
color: #eee;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -146,14 +220,19 @@
|
||||
.user-title {
|
||||
font-size: 10px;
|
||||
color: #888;
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
color: #ffd700;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-wrapper {
|
||||
background-color: #000;
|
||||
background-color: transparent; /* Changed from black */
|
||||
margin-bottom: 10px;
|
||||
padding: 0 10px;
|
||||
|
||||
/* Override Taro UI default white background */
|
||||
.at-tabs {
|
||||
@@ -163,94 +242,124 @@
|
||||
|
||||
.at-tabs__header {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-bottom: none; /* Removed border */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.at-tabs__item {
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
padding: 12px 24px;
|
||||
font-size: 17px; /* Increased from 16px */
|
||||
padding: 14px 20px; /* Larger touch target */
|
||||
transition: all 0.3s;
|
||||
|
||||
&--active {
|
||||
color: #00b96b;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #fff; /* White active text */
|
||||
font-weight: 700;
|
||||
font-size: 20px; /* Increased from 19px */
|
||||
text-shadow: 0 0 10px rgba(0, 185, 107, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.at-tabs__item-underline {
|
||||
background-color: #00b96b;
|
||||
bottom: 0;
|
||||
height: 4px; /* Slightly thicker */
|
||||
border-radius: 2px;
|
||||
bottom: 5px;
|
||||
width: 28px !important; /* Short underline style */
|
||||
margin-left: calc(50% - 14px); /* Center specific width underline */
|
||||
}
|
||||
}
|
||||
|
||||
.topic-list {
|
||||
padding: 10px;
|
||||
padding: 10px 16px;
|
||||
|
||||
.topic-card {
|
||||
background: rgba(20,20,20,0.6);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
border-radius: 16px;
|
||||
padding: 24px; /* Increased from 20px */
|
||||
margin-bottom: 24px; /* Increased spacing */
|
||||
position: relative;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
animation: fadeInUp 0.5s ease-out backwards; /* Apply animation */
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background: #252525;
|
||||
}
|
||||
|
||||
&.pinned {
|
||||
border-color: rgba(0, 185, 107, 0.4);
|
||||
box-shadow: 0 0 10px rgba(0, 185, 107, 0.1);
|
||||
border-color: rgba(0, 185, 107, 0.3);
|
||||
background: linear-gradient(180deg, rgba(0, 185, 107, 0.05) 0%, #1e1e1e 100%);
|
||||
box-shadow: 0 8px 20px rgba(0, 185, 107, 0.1);
|
||||
}
|
||||
|
||||
/* Animation delay for staggered effect - simplistic approach (nth-child logic is better in CSS-in-JS or fixed list) */
|
||||
&:nth-child(1) { animation-delay: 0.1s; }
|
||||
&:nth-child(2) { animation-delay: 0.2s; }
|
||||
&:nth-child(3) { animation-delay: 0.3s; }
|
||||
&:nth-child(4) { animation-delay: 0.4s; }
|
||||
&:nth-child(5) { animation-delay: 0.5s; }
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
.tag {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #444;
|
||||
font-size: 12px; /* Slightly larger */
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: #aaa;
|
||||
|
||||
&.pinned-tag {
|
||||
border-color: #ff4d4f;
|
||||
background: rgba(255, 77, 79, 0.15);
|
||||
color: #ff4d4f;
|
||||
border: 1px solid rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
&.verified-tag {
|
||||
border-color: #00b96b;
|
||||
background: rgba(0, 185, 107, 0.15);
|
||||
color: #00b96b;
|
||||
border: 1px solid rgba(0, 185, 107, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-size: 22px; /* Increased from 19px */
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
flex: 1;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
margin-bottom: 12px;
|
||||
font-size: 17px; /* Increased from 15px */
|
||||
color: #ccc; /* Slightly brighter for better contrast */
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.7;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-line-clamp: 3; /* Show 3 lines */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
max-height: 150px;
|
||||
border-radius: 8px;
|
||||
max-height: 220px; /* Taller image preview */
|
||||
object-fit: cover;
|
||||
display: block; /* Remove inline spacing */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,36 +367,47 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
font-size: 14px; /* Increased from 13px */
|
||||
color: #888;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(255,255,255,0.05);
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 10px;
|
||||
|
||||
.avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 40px; /* Larger avatar */
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.nickname {
|
||||
color: #ccc;
|
||||
font-weight: 500;
|
||||
|
||||
&.star {
|
||||
color: #ffd700;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 20px;
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
|
||||
.at-icon {
|
||||
font-size: 18px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,26 +416,42 @@
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
|
||||
&::before {
|
||||
content: '📭'; /* Simple icon */
|
||||
display: block;
|
||||
font-size: 40px;
|
||||
margin-bottom: 10px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.fab {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 40px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: #00b96b;
|
||||
right: 24px;
|
||||
bottom: 50px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: linear-gradient(135deg, #00b96b 0%, #009456 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 12px rgba(0, 185, 107, 0.4);
|
||||
box-shadow: 0 4px 20px rgba(0, 185, 107, 0.5);
|
||||
z-index: 100;
|
||||
animation: pulse 3s infinite;
|
||||
transition: transform 0.2s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.9);
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.at-icon {
|
||||
font-size: 24px;
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,12 +181,12 @@ const ForumList = () => {
|
||||
placeholder='搜索感兴趣的话题...'
|
||||
/>
|
||||
<View className='create-btn' onClick={navigateToCreate}>
|
||||
<AtIcon value='add' size='16' color='#fff' />
|
||||
<Text style={{marginLeft: '4px'}}>发布新帖</Text>
|
||||
<AtIcon value='add' size='20' color='#fff' />
|
||||
<Text style={{marginLeft: '6px'}}>发布</Text>
|
||||
</View>
|
||||
<View className='create-btn' onClick={navigateToActivity} style={{marginLeft: '10px', background: 'rgba(255,255,255,0.2)'}}>
|
||||
<AtIcon value='calendar' size='16' color='#fff' />
|
||||
<Text style={{marginLeft: '4px'}}>社区活动</Text>
|
||||
<View className='create-btn' onClick={navigateToActivity} style={{marginLeft: '10px', background: 'linear-gradient(135deg, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0.05) 100%)', backdropFilter: 'blur(5px)'}}>
|
||||
<AtIcon value='calendar' size='20' color='#fff' />
|
||||
<Text style={{marginLeft: '6px'}}>活动</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -273,13 +273,17 @@ const ForumList = () => {
|
||||
<Text className={`nickname ${item.author_info?.is_star ? 'star' : ''}`}>
|
||||
{item.author_info?.nickname || '匿名'}
|
||||
</Text>
|
||||
<Text style={{color: '#555', fontSize: '10px'}}>•</Text>
|
||||
<Text style={{color: '#666', fontSize: '11px'}}>{new Date(item.created_at).toLocaleDateString()}</Text>
|
||||
</View>
|
||||
<View className='stats'>
|
||||
<View className='stat-item'>
|
||||
<Text>👁 {item.view_count || 0}</Text>
|
||||
<AtIcon value='eye' size='14' color='#777' />
|
||||
<Text>{item.view_count || 0}</Text>
|
||||
</View>
|
||||
<View className='stat-item'>
|
||||
<Text>💬 {item.replies?.length || 0}</Text>
|
||||
<AtIcon value='message' size='14' color='#777' />
|
||||
<Text>{item.replies?.length || 0}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -115,6 +115,188 @@
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
// News Ticker Styles
|
||||
.news-section {
|
||||
padding: 0 32px 40px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
.news-inner {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.news-icon-box {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
|
||||
.news-icon {
|
||||
font-size: 32px;
|
||||
animation: pulse-icon 2s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.news-swiper {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.news-tag {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: #000;
|
||||
background: var(--primary-cyan);
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.news-title {
|
||||
font-size: 26px;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-icon {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.8; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
// Activity Banner Styles
|
||||
.activity-section {
|
||||
padding-bottom: 60px;
|
||||
|
||||
.section-header {
|
||||
padding: 0 32px;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
|
||||
.section-title {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 20px rgba(0, 240, 255, 0.2);
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24px;
|
||||
color: var(--text-secondary);
|
||||
|
||||
.arrow {
|
||||
margin-left: 8px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
&:active .arrow {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.activity-swiper {
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
.activity-swiper-item {
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #111;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
|
||||
.activity-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.activity-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.4) 50%, transparent 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.activity-info {
|
||||
transform: translateY(0);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.activity-status {
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
background: var(--primary-purple);
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
align-self: flex-start;
|
||||
box-shadow: 0 0 10px rgba(189, 0, 255, 0.4);
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.3;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.activity-time {
|
||||
font-size: 24px;
|
||||
color: rgba(255,255,255,0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-grid {
|
||||
padding: 0 32px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { View, Text, Image, ScrollView, Button } from '@tarojs/components'
|
||||
import { View, Text, Image, ScrollView, Button, Swiper, SwiperItem } from '@tarojs/components'
|
||||
import Taro, { useLoad } from '@tarojs/taro'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { getConfigs } from '../../api'
|
||||
import { getConfigs, getAnnouncements, getActivities } from '../../api'
|
||||
import ParticleBackground from '../../components/ParticleBackground'
|
||||
import './index.scss'
|
||||
|
||||
export default function Index() {
|
||||
const [products, setProducts] = useState<any[]>([])
|
||||
const [announcements, setAnnouncements] = useState<any[]>([])
|
||||
const [activities, setActivities] = useState<any[]>([])
|
||||
const [typedText, setTypedText] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const fullText = "未来已来 AI 核心驱动"
|
||||
|
||||
useLoad(() => {
|
||||
fetchProducts()
|
||||
fetchData()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -26,13 +28,48 @@ export default function Index() {
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const fetchProducts = async () => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
try {
|
||||
const res: any = await getConfigs()
|
||||
const list = Array.isArray(res) ? res : (res.results || res.data || [])
|
||||
setProducts(list)
|
||||
// Parallel fetch for better performance
|
||||
const [productsRes, announcementsRes, activitiesRes] = await Promise.all([
|
||||
getConfigs().catch(() => ({ results: [] })),
|
||||
getAnnouncements().catch(() => ({ results: [] })),
|
||||
getActivities().catch(() => ({ results: [] }))
|
||||
])
|
||||
|
||||
const productList = Array.isArray(productsRes) ? productsRes : (productsRes?.results || productsRes?.data || [])
|
||||
setProducts(productList)
|
||||
|
||||
const announcementList = Array.isArray(announcementsRes) ? announcementsRes : (announcementsRes?.results || announcementsRes?.data || [])
|
||||
// Mock data for demo if empty
|
||||
if (announcementList.length === 0) {
|
||||
announcementList.push(
|
||||
{ id: 101, title: 'Quant Speed AI 开发者大会即将开启报名' },
|
||||
{ id: 102, title: '新品发布:AI小智 V2 性能提升300%' },
|
||||
{ id: 103, title: '社区活动:分享你的边缘计算项目赢大奖' }
|
||||
)
|
||||
}
|
||||
setAnnouncements(announcementList)
|
||||
|
||||
const activityList = Array.isArray(activitiesRes) ? activitiesRes : (activitiesRes?.results || activitiesRes?.data || [])
|
||||
// Mock data for demo if empty
|
||||
if (activityList.length === 0) {
|
||||
activityList.push({
|
||||
id: 201,
|
||||
title: '2025 AI 硬件黑客马拉松',
|
||||
start_time: '2025-05-20T10:00:00',
|
||||
cover_image: 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?q=80&w=2070&auto=format&fit=crop'
|
||||
}, {
|
||||
id: 202,
|
||||
title: '边缘计算实战训练营',
|
||||
start_time: '2025-06-15T09:00:00',
|
||||
cover_image: 'https://images.unsplash.com/photo-1518770660439-4636190af475?q=80&w=2070&auto=format&fit=crop'
|
||||
})
|
||||
}
|
||||
setActivities(activityList)
|
||||
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
setError(err.errMsg || '加载失败,请检查网络')
|
||||
@@ -45,6 +82,19 @@ export default function Index() {
|
||||
Taro.navigateTo({ url: `/pages/goods/detail?id=${id}` })
|
||||
}
|
||||
|
||||
const goToAnnouncement = (id: number) => {
|
||||
// Assuming generic topic detail or specific announcement page
|
||||
Taro.navigateTo({ url: `/subpackages/forum/detail/detail?id=${id}` })
|
||||
}
|
||||
|
||||
const goToActivity = (id: number) => {
|
||||
Taro.navigateTo({ url: `/subpackages/forum/activity/detail?id=${id}` })
|
||||
}
|
||||
|
||||
const goToActivityList = () => {
|
||||
Taro.navigateTo({ url: `/subpackages/forum/activity/index` })
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
<ParticleBackground />
|
||||
@@ -64,6 +114,75 @@ export default function Index() {
|
||||
<Text className='subtitle'>量迹 AI 硬件为您提供最强大的边缘计算能力</Text>
|
||||
</View>
|
||||
|
||||
{/* News Ticker */}
|
||||
{!loading && announcements.length > 0 && (
|
||||
<View className='news-section fade-in-up'>
|
||||
<View className='news-inner'>
|
||||
<View className='news-icon-box'>
|
||||
<Text className='news-icon'>⚡</Text>
|
||||
</View>
|
||||
<Swiper
|
||||
className='news-swiper'
|
||||
vertical
|
||||
circular
|
||||
autoplay
|
||||
interval={4000}
|
||||
duration={600}
|
||||
>
|
||||
{announcements.map(item => (
|
||||
<SwiperItem key={item.id} onClick={() => goToAnnouncement(item.id)}>
|
||||
<View className='news-item'>
|
||||
<Text className='news-tag'>NEWS</Text>
|
||||
<Text className='news-title'>{item.title}</Text>
|
||||
</View>
|
||||
</SwiperItem>
|
||||
))}
|
||||
</Swiper>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Activity Banner */}
|
||||
{!loading && activities.length > 0 && (
|
||||
<View className='activity-section fade-in-up' style={{ animationDelay: '0.1s' }}>
|
||||
<View className='section-header'>
|
||||
<Text className='section-title'>近期活动 / EVENTS</Text>
|
||||
<View className='more-btn' onClick={goToActivityList}>
|
||||
<Text>MORE</Text>
|
||||
<Text className='arrow'>→</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Swiper
|
||||
className='activity-swiper'
|
||||
circular
|
||||
autoplay
|
||||
interval={5000}
|
||||
duration={600}
|
||||
previousMargin='30px'
|
||||
nextMargin='30px'
|
||||
>
|
||||
{activities.map(item => (
|
||||
<SwiperItem key={item.id} className='activity-swiper-item' onClick={() => goToActivity(item.id)}>
|
||||
<View className='activity-card'>
|
||||
<Image
|
||||
src={item.display_banner_url || item.banner_url || item.cover_image || 'https://via.placeholder.com/600x300'}
|
||||
mode='aspectFill'
|
||||
className='activity-img'
|
||||
/>
|
||||
<View className='activity-overlay'>
|
||||
<View className='activity-info'>
|
||||
<Text className='activity-status'>报名中</Text>
|
||||
<Text className='activity-title'>{item.title}</Text>
|
||||
<Text className='activity-time'>{item.start_time ? item.start_time.split('T')[0] : 'TBD'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</SwiperItem>
|
||||
))}
|
||||
</Swiper>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<View className='skeleton-wrapper'>
|
||||
{[1, 2, 3].map(i => (
|
||||
@@ -73,7 +192,7 @@ export default function Index() {
|
||||
) : error ? (
|
||||
<View className='status-box'>
|
||||
<Text className='error-text'>{error}</Text>
|
||||
<Button className='btn-retry' onClick={fetchProducts}>重试</Button>
|
||||
<Button className='btn-retry' onClick={fetchData}>重试</Button>
|
||||
</View>
|
||||
) : (
|
||||
<View className='product-grid'>
|
||||
@@ -81,7 +200,7 @@ export default function Index() {
|
||||
<View
|
||||
key={item.id}
|
||||
className='card fade-in-up'
|
||||
style={{ animationDelay: `${index * 0.1}s` }}
|
||||
style={{ animationDelay: `${0.2 + index * 0.1}s` }}
|
||||
onClick={() => goToDetail(item.id)}
|
||||
>
|
||||
<View className='card-cover'>
|
||||
|
||||
@@ -28,26 +28,87 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.nav-btn-wrapper {
|
||||
margin-top: 30px;
|
||||
.vc-promo-container {
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.vc-info-card {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03));
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20px;
|
||||
padding: 20px 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.05), transparent);
|
||||
transform: translateX(-100%);
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
|
||||
&::before {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
font-size: 40px;
|
||||
margin-right: 20px;
|
||||
filter: drop-shadow(0 0 10px rgba(255, 255, 0, 0.3));
|
||||
}
|
||||
|
||||
.info-content {
|
||||
text-align: left;
|
||||
|
||||
.info-title {
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.info-desc {
|
||||
color: #ccc;
|
||||
font-size: 24px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
background: linear-gradient(90deg, #00b96b, #00f0ff);
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
font-size: 28px;
|
||||
padding: 0 40px;
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
border-radius: 40px;
|
||||
padding: 0 50px;
|
||||
height: 90px;
|
||||
line-height: 90px;
|
||||
border-radius: 45px;
|
||||
border: none;
|
||||
box-shadow: 0 0 20px rgba(0, 185, 107, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
transition: all 0.3s ease;
|
||||
margin: 0;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
|
||||
@@ -37,12 +37,20 @@ export default function ServicesIndex() {
|
||||
<Text className='title'>AI 全栈<Text className='highlight'>解决方案</Text></Text>
|
||||
<Text className='subtitle'>从数据处理到模型部署,我们为您提供一站式 AI 基础设施服务。</Text>
|
||||
|
||||
<View className='nav-btn-wrapper'>
|
||||
<View className='vc-promo-container'>
|
||||
<View className='vc-info-card' onClick={() => Taro.navigateTo({ url: '/pages/courses/index' })}>
|
||||
<View className='info-icon'>💡</View>
|
||||
<View className='info-content'>
|
||||
<Text className='info-title'>AI + VC 课程</Text>
|
||||
<Text className='info-desc'>深度解析 AI 如何赋能创投,掌握技术变现的核心逻辑</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
className='nav-btn'
|
||||
onClick={() => Taro.navigateTo({ url: '/pages/courses/index' })}
|
||||
>
|
||||
探索 VB 课程
|
||||
探索 VC 课程
|
||||
<Text className='arrow'>→</Text>
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
@@ -19,6 +19,8 @@ export default function UserIndex() {
|
||||
const goInvite = () => Taro.navigateTo({ url: '/subpackages/distributor/invite' })
|
||||
const goWithdraw = () => Taro.navigateTo({ url: '/subpackages/distributor/withdraw' })
|
||||
|
||||
const goActivityList = (tab = 'all') => Taro.navigateTo({ url: `/subpackages/forum/activity/index?tab=${tab}` })
|
||||
|
||||
const handleAddress = async () => {
|
||||
try {
|
||||
const res = await Taro.chooseAddress()
|
||||
@@ -240,8 +242,8 @@ export default function UserIndex() {
|
||||
title: '基础服务',
|
||||
items: [
|
||||
{ title: '我的订单', icon: '📦', action: goOrders },
|
||||
{ title: '地址管理', icon: '📍', action: handleAddress },
|
||||
{ title: '新增地址', icon: '📝', action: handleAddress },
|
||||
{ title: '地址管理', icon: '📝', action: handleAddress },
|
||||
{ title: '活动管理', icon: '⌚️', action: () => goActivityList('mine') },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,78 +1,361 @@
|
||||
.activity-detail {
|
||||
padding-bottom: 80px;
|
||||
.activity-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #050505;
|
||||
color: #fff;
|
||||
padding-bottom: 100px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
|
||||
.cover {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.meta-box {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.meta-row {
|
||||
.loading-container, .error-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #888;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero-section {
|
||||
position: relative;
|
||||
height: 320px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.hero-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.hero-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to top, #050505 0%, rgba(5,5,5,0.6) 50%, rgba(0,0,0,0.2) 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.status-tag {
|
||||
align-self: flex-start;
|
||||
background: var(--primary-cyan, #00f0ff);
|
||||
color: #000;
|
||||
font-weight: 800;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 0 10px rgba(0, 240, 255, 0.4);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
text-shadow: 0 2px 10px rgba(0,0,0,0.5);
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 0 24px;
|
||||
transform: translateY(-20px);
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Info Grid */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
|
||||
.info-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.info-text {
|
||||
margin-left: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: rgba(255,255,255,0.5);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
border-left: 4px solid #00b96b;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.description {
|
||||
.value {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-bar {
|
||||
/* Stats Section */
|
||||
.stats-section {
|
||||
background: #111;
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
margin-bottom: 32px;
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
|
||||
.stats-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.stats-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.stats-count {
|
||||
.current {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
color: var(--primary-cyan, #00f0ff);
|
||||
}
|
||||
.divider {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin: 0 4px;
|
||||
}
|
||||
.max {
|
||||
font-size: 18px;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
height: 8px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #00f0ff, #bd00ff);
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease-out;
|
||||
}
|
||||
|
||||
.progress-glow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 10px;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
filter: blur(4px);
|
||||
opacity: 0.8;
|
||||
transform: translateX(-50%);
|
||||
transition: left 0.5s ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Detail Section */
|
||||
.detail-section {
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.title-text {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
margin-right: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.line {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(255,255,255,0.2), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.rich-text-wrapper {
|
||||
color: #ccc;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
|
||||
image {
|
||||
max-width: 100%;
|
||||
border-radius: 12px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
/* Markdown Styling */
|
||||
h1, h2, h3, h4, h5, h6 { margin-top: 24px; margin-bottom: 16px; color: #fff; font-weight: 700; line-height: 1.4; }
|
||||
h1 { font-size: 24px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 8px; }
|
||||
h2 { font-size: 20px; border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 6px; }
|
||||
h3 { font-size: 18px; }
|
||||
|
||||
p { margin-bottom: 16px; }
|
||||
|
||||
strong { font-weight: 800; color: #fff; }
|
||||
em { font-style: italic; color: #aaa; }
|
||||
del { text-decoration: line-through; color: #666; }
|
||||
|
||||
ul, ol { margin-bottom: 16px; padding-left: 20px; }
|
||||
li { margin-bottom: 6px; list-style-position: outside; }
|
||||
ul li { list-style-type: disc; }
|
||||
ol li { list-style-type: decimal; }
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid var(--primary-cyan, #00f0ff);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 12px 16px;
|
||||
margin: 16px 0;
|
||||
border-radius: 4px;
|
||||
color: #bbb;
|
||||
font-style: italic;
|
||||
|
||||
p { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
a { color: var(--primary-cyan, #00f0ff); text-decoration: none; border-bottom: 1px solid transparent; transition: border-color 0.2s; }
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: none;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 16px 0;
|
||||
font-size: 14px;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
|
||||
th, td {
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: rgba(255,255,255,0.05);
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background: rgba(255,255,255,0.02);
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 3px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
color: #ff7875;
|
||||
font-size: 14px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #161616;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
overflow-x: auto;
|
||||
margin: 16px 0;
|
||||
border: 1px solid #333;
|
||||
box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
|
||||
|
||||
code {
|
||||
background: transparent;
|
||||
color: #a6e22e;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer Action Bar */
|
||||
.footer-action-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
padding: 10px 20px;
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||
padding-bottom: calc(10px + env(safe-area-inset-bottom));
|
||||
background: rgba(10, 10, 10, 0.9);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
padding: 16px 24px;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
z-index: 100;
|
||||
|
||||
.btn-signup {
|
||||
width: 100%;
|
||||
background: #00b96b;
|
||||
.left-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.price {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
color: var(--primary-cyan, #00f0ff);
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
margin: 0;
|
||||
padding: 0 40px;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
border-radius: 28px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
|
||||
&[disabled] {
|
||||
background: #ccc;
|
||||
color: #fff;
|
||||
&.active {
|
||||
background: linear-gradient(90deg, #00f0ff, #00b96b);
|
||||
color: #000;
|
||||
box-shadow: 0 4px 15px rgba(0, 240, 255, 0.3);
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import Taro, { useRouter, useShareAppMessage } from '@tarojs/taro'
|
||||
import Taro, { useRouter, useShareAppMessage, useDidShow } from '@tarojs/taro'
|
||||
import { View, Text, Image, Button, RichText } from '@tarojs/components'
|
||||
import { AtIcon, AtProgress, AtModal, AtModalHeader, AtModalContent, AtModalAction, AtInput } from 'taro-ui'
|
||||
import { getActivityDetail, signupActivity } from '../../../api'
|
||||
import { marked } from 'marked'
|
||||
import './detail.scss'
|
||||
@@ -13,19 +14,41 @@ const ActivityDetail = () => {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [htmlContent, setHtmlContent] = useState('')
|
||||
const [signupPercentage, setSignupPercentage] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
// Signup Form State
|
||||
const [showSignupModal, setShowSignupModal] = useState(false)
|
||||
const [formData, setFormData] = useState<any>({})
|
||||
|
||||
// Function to refresh data, can be used in useDidShow
|
||||
const refreshData = () => {
|
||||
if (id) {
|
||||
fetchDetail()
|
||||
}
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
refreshData()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
refreshData()
|
||||
}, [id])
|
||||
|
||||
const fetchDetail = async () => {
|
||||
try {
|
||||
const res = await getActivityDetail(Number(id))
|
||||
setActivity(res.data)
|
||||
if (res.data.description) {
|
||||
const html = marked.parse(res.data.description)
|
||||
const data = res.data || res
|
||||
setActivity(data)
|
||||
|
||||
// Calculate signup progress
|
||||
if (data.max_participants > 0) {
|
||||
const percent = Math.min(100, Math.round((data.current_signups || 0) / data.max_participants * 100))
|
||||
setSignupPercentage(percent)
|
||||
}
|
||||
|
||||
if (data.description) {
|
||||
const html = marked.parse(data.description)
|
||||
setHtmlContent((html as string).replace(/<img/g, '<img style="max-width:100%;border-radius:8px;"'))
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -40,23 +63,58 @@ const ActivityDetail = () => {
|
||||
const token = Taro.getStorageSync('token')
|
||||
if (!token) {
|
||||
Taro.showToast({ title: '请先登录', icon: 'none' })
|
||||
// Optional: Redirect to login
|
||||
// Taro.navigateTo({ url: '/pages/user/login' })
|
||||
return
|
||||
}
|
||||
|
||||
// Check if form config exists
|
||||
if (activity.signup_form_config && activity.signup_form_config.length > 0) {
|
||||
setFormData({}) // Reset form
|
||||
setShowSignupModal(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Direct signup if no config
|
||||
submitSignup({})
|
||||
}
|
||||
|
||||
const submitSignup = async (data: any) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await signupActivity(Number(id))
|
||||
await signupActivity(Number(id), { signup_info: data })
|
||||
Taro.showToast({ title: '报名成功', icon: 'success' })
|
||||
setShowSignupModal(false)
|
||||
fetchDetail() // Refresh status
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
const msg = error.response?.data?.error || '报名失败'
|
||||
const msg = error.response?.data?.error || error.message || '报名失败'
|
||||
Taro.showToast({ title: msg, icon: 'none' })
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFormChange = (fieldName, value) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[fieldName]: value
|
||||
}))
|
||||
}
|
||||
|
||||
const handleModalConfirm = () => {
|
||||
// Validate
|
||||
if (activity.signup_form_config) {
|
||||
for (const field of activity.signup_form_config) {
|
||||
if (field.required && !formData[field.name]) {
|
||||
Taro.showToast({ title: `请填写${field.label}`, icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
submitSignup(formData)
|
||||
}
|
||||
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: activity?.title || '社区活动',
|
||||
@@ -64,47 +122,122 @@ const ActivityDetail = () => {
|
||||
}
|
||||
})
|
||||
|
||||
if (loading) return <View>Loading...</View>
|
||||
if (!activity) return <View>活动不存在</View>
|
||||
if (loading) return <View className='loading-container'><Text>Loading...</Text></View>
|
||||
if (!activity) return <View className='error-container'><Text>活动不存在</Text></View>
|
||||
|
||||
const isFull = activity.max_participants > 0 && (activity.current_signups || 0) >= activity.max_participants
|
||||
const isEnded = new Date(activity.end_time) < new Date()
|
||||
const canSignup = activity.is_active && !isFull && !isEnded && !activity.has_signed_up
|
||||
|
||||
return (
|
||||
<View className='activity-detail'>
|
||||
<Image src={activity.cover_image} mode='widthFix' className='cover' />
|
||||
|
||||
<View className='content'>
|
||||
<Text className='title'>{activity.title}</Text>
|
||||
|
||||
<View className='meta-box'>
|
||||
<View className='meta-row'>
|
||||
<Text className='label'>时间:</Text>
|
||||
<Text>{new Date(activity.start_time).toLocaleString()} ~ {new Date(activity.end_time).toLocaleString()}</Text>
|
||||
<View className='activity-detail-page'>
|
||||
{/* Hero Banner */}
|
||||
<View className='hero-section'>
|
||||
<Image
|
||||
src={activity.display_banner_url || activity.banner_url || activity.cover_image || 'https://via.placeholder.com/800x600'}
|
||||
mode='aspectFill'
|
||||
className='hero-bg'
|
||||
/>
|
||||
<View className='hero-overlay'>
|
||||
<View className='status-tag'>
|
||||
{isEnded ? '已结束' : (isFull ? '名额已满' : '报名中')}
|
||||
</View>
|
||||
<View className='meta-row'>
|
||||
<Text className='label'>地点:</Text>
|
||||
<Text>{activity.location || '线上活动'}</Text>
|
||||
</View>
|
||||
<View className='meta-row'>
|
||||
<Text className='label'>名额:</Text>
|
||||
<Text>{activity.current_signups} / {activity.max_participants > 0 ? activity.max_participants : '不限'}</Text>
|
||||
<Text className='hero-title'>{activity.title}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='description'>
|
||||
<View className='section-title'>活动详情</View>
|
||||
<View className='main-content'>
|
||||
{/* Info Cards */}
|
||||
<View className='info-grid'>
|
||||
<View className='info-card time'>
|
||||
<AtIcon value='clock' size='18' color='#00f0ff' />
|
||||
<View className='info-text'>
|
||||
<Text className='label'>开始时间</Text>
|
||||
<Text className='value'>{new Date(activity.start_time).toLocaleString()}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className='info-card location'>
|
||||
<AtIcon value='map-pin' size='18' color='#bd00ff' />
|
||||
<View className='info-text'>
|
||||
<Text className='label'>活动地点</Text>
|
||||
<Text className='value'>{activity.location || '线上直播'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Signup Stats */}
|
||||
<View className='stats-section'>
|
||||
<View className='stats-header'>
|
||||
<Text className='stats-title'>报名进度</Text>
|
||||
<Text className='stats-count'>
|
||||
<Text className='current'>{activity.current_signups || 0}</Text>
|
||||
<Text className='divider'>/</Text>
|
||||
<Text className='max'>{activity.max_participants > 0 ? activity.max_participants : '∞'}</Text>
|
||||
</Text>
|
||||
</View>
|
||||
<View className='progress-bar-container'>
|
||||
<View className='progress-bar' style={{width: `${signupPercentage}%`}} />
|
||||
<View className='progress-glow' style={{left: `${signupPercentage}%`}} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Detail Content */}
|
||||
<View className='detail-section'>
|
||||
<View className='section-header'>
|
||||
<Text className='title-text'>活动详情</Text>
|
||||
<View className='line' />
|
||||
</View>
|
||||
<View className='rich-text-wrapper'>
|
||||
<RichText nodes={htmlContent} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='footer-bar'>
|
||||
{/* Footer Action Bar */}
|
||||
<View className='footer-action-bar'>
|
||||
<View className='left-info'>
|
||||
<Text className='price'>免费</Text>
|
||||
<Text className='desc'>限时活动</Text>
|
||||
</View>
|
||||
<Button
|
||||
className='btn-signup'
|
||||
type='primary'
|
||||
disabled={activity.has_signed_up || activity.status !== 'open' || submitting}
|
||||
className={`action-btn ${canSignup ? 'active' : 'disabled'}`}
|
||||
disabled={!canSignup || submitting}
|
||||
onClick={handleSignup}
|
||||
>
|
||||
{activity.has_signed_up ? '已报名' : (activity.status === 'open' ? '立即报名' : '无法报名')}
|
||||
{submitting ? '提交中...' : (
|
||||
activity.has_signed_up ? '您已报名' : (
|
||||
isEnded ? '活动已结束' : (
|
||||
isFull ? '名额已满' : '立即报名'
|
||||
)
|
||||
)
|
||||
)}
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
{/* Signup Form Modal */}
|
||||
<AtModal isOpened={showSignupModal} onClose={() => setShowSignupModal(false)}>
|
||||
<AtModalHeader>填写报名信息</AtModalHeader>
|
||||
<AtModalContent>
|
||||
<View className='signup-form'>
|
||||
{activity.signup_form_config && activity.signup_form_config.map((field, idx) => (
|
||||
<AtInput
|
||||
key={idx}
|
||||
name={field.name}
|
||||
title={field.label}
|
||||
type={field.type || 'text'}
|
||||
placeholder={`请输入${field.label}`}
|
||||
value={formData[field.name]}
|
||||
onChange={(val) => handleFormChange(field.name, val)}
|
||||
required={field.required}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</AtModalContent>
|
||||
<AtModalAction>
|
||||
<Button onClick={() => setShowSignupModal(false)}>取消</Button>
|
||||
<Button onClick={handleModalConfirm} style={{color: '#00b96b'}}>提交报名</Button>
|
||||
</AtModalAction>
|
||||
</AtModal>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,108 +1,163 @@
|
||||
.activity-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding-bottom: 20px;
|
||||
background-color: #050505;
|
||||
padding-bottom: 40px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
padding: 10px 0;
|
||||
.tabs-header {
|
||||
background: rgba(10, 10, 10, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
padding: 12px 24px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
||||
z-index: 100;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
|
||||
.tabs-bg {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
padding: 10px 0;
|
||||
position: relative;
|
||||
font-size: 15px;
|
||||
color: #888;
|
||||
padding: 8px 0;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
font-weight: 500;
|
||||
|
||||
&.active {
|
||||
color: #00b96b;
|
||||
font-weight: bold;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 30px;
|
||||
height: 3px;
|
||||
background: #00b96b;
|
||||
border-radius: 2px;
|
||||
color: #000;
|
||||
background: var(--primary-cyan, #00f0ff);
|
||||
font-weight: 700;
|
||||
box-shadow: 0 2px 8px rgba(0, 240, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-container {
|
||||
padding: 15px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 50px 0;
|
||||
color: #666;
|
||||
padding: 80px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.card-cover-wrapper {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(10px);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 15px;
|
||||
padding: 20px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
background: #eee;
|
||||
color: #666;
|
||||
|
||||
&.open { background: #e6ffed; color: #00b96b; }
|
||||
&.upcoming { background: #e6f7ff; color: #1890ff; }
|
||||
&.ended { background: #f5f5f5; color: #999; }
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
color: #999;
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.signup-status {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
color: #00b96b;
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
|
||||
.participants {
|
||||
.highlight {
|
||||
color: var(--primary-cyan, #00f0ff);
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
.total {
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-view {
|
||||
margin: 0;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
padding: 0 16px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import Taro, { usePullDownRefresh } from '@tarojs/taro'
|
||||
import Taro, { usePullDownRefresh, useRouter, useDidShow } from '@tarojs/taro'
|
||||
import { View, Text, Image, Button } from '@tarojs/components'
|
||||
import { getActivities, getMySignups } from '../../../api'
|
||||
import './index.scss'
|
||||
|
||||
const ActivityList = () => {
|
||||
const router = useRouter()
|
||||
const [activities, setActivities] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [tab, setTab] = useState<'all' | 'mine'>('all')
|
||||
const [mySignups, setMySignups] = useState<any[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (router.params.tab === 'mine') {
|
||||
setTab('mine')
|
||||
}
|
||||
}, [router.params.tab])
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
@@ -33,6 +40,10 @@ const ActivityList = () => {
|
||||
fetchData()
|
||||
}, [tab])
|
||||
|
||||
useDidShow(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData()
|
||||
})
|
||||
@@ -54,34 +65,60 @@ const ActivityList = () => {
|
||||
const renderList = (list) => {
|
||||
if (list.length === 0 && !loading) return <View className='empty'>暂无活动</View>
|
||||
|
||||
return list.map(item => (
|
||||
<View key={item.id} className='activity-card' onClick={() => goDetail(item.id)}>
|
||||
<Image src={item.cover_image || 'https://via.placeholder.com/350x150'} mode='aspectFill' className='cover' />
|
||||
return list.map(item => {
|
||||
// Handle API structure differences
|
||||
// For 'mine' tab, item is the signup record. activity_info is the full activity object.
|
||||
const activity = tab === 'mine' ? (item.activity_info || item.activity || item) : item
|
||||
|
||||
return (
|
||||
<View key={activity.id} className='activity-card' onClick={() => goDetail(activity.id)}>
|
||||
<View className='card-cover-wrapper'>
|
||||
<Image
|
||||
src={activity.display_banner_url || activity.banner_url || activity.cover_image || 'https://via.placeholder.com/350x150'}
|
||||
mode='aspectFill'
|
||||
className='cover'
|
||||
/>
|
||||
<View className='status-tag'>
|
||||
{getStatusText(activity.status)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='info'>
|
||||
<View className='header'>
|
||||
<Text className='title'>{item.title}</Text>
|
||||
<Text className={`status ${item.status}`}>{getStatusText(item.status)}</Text>
|
||||
<Text className='title'>{activity.title}</Text>
|
||||
</View>
|
||||
<View className='meta'>
|
||||
<Text>📅 {new Date(item.start_time).toLocaleDateString()}</Text>
|
||||
<Text>📍 {item.location || '线上活动'}</Text>
|
||||
<View className='meta-item'>
|
||||
<Text className='icon'>📅</Text>
|
||||
<Text>{new Date(activity.start_time).toLocaleDateString()}</Text>
|
||||
</View>
|
||||
{tab === 'mine' && (
|
||||
<View className='signup-status'>
|
||||
<Text>已报名</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className='meta-item'>
|
||||
<Text className='icon'>📍</Text>
|
||||
<Text>{activity.location || '线上活动'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
|
||||
<View className='card-footer'>
|
||||
<View className='participants'>
|
||||
<Text className='highlight'>{activity.current_signups || 0}</Text>
|
||||
<Text className='total'>/{activity.max_participants > 0 ? activity.max_participants : '∞'} 已报名</Text>
|
||||
</View>
|
||||
<Button className='btn-view'>查看详情</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='activity-page'>
|
||||
<View className='tabs'>
|
||||
<View className='tabs-header'>
|
||||
<View className='tabs-bg'>
|
||||
<View className={`tab ${tab === 'all' ? 'active' : ''}`} onClick={() => setTab('all')}>精彩活动</View>
|
||||
<View className={`tab ${tab === 'mine' ? 'active' : ''}`} onClick={() => setTab('mine')}>我的报名</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='list-container'>
|
||||
{renderList(tab === 'all' ? activities : mySignups)}
|
||||
|
||||
@@ -1,9 +1,150 @@
|
||||
.create-topic-page {
|
||||
min-height: 100vh;
|
||||
background-color: #000;
|
||||
padding: 20px;
|
||||
color: #fff;
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
|
||||
.action-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
margin-bottom: 30px;
|
||||
background: #111;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #333;
|
||||
|
||||
.preview-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
color: #e0e0e0;
|
||||
|
||||
image {
|
||||
max-width: 100%;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 { margin-top: 24px; margin-bottom: 12px; color: #fff; font-weight: 700; line-height: 1.4; }
|
||||
h1 { font-size: 22px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 8px; }
|
||||
h2 { font-size: 18px; border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 6px; }
|
||||
h3 { font-size: 16px; }
|
||||
|
||||
p { margin-bottom: 14px; }
|
||||
|
||||
strong { font-weight: 800; color: #fff; }
|
||||
em { font-style: italic; color: #aaa; }
|
||||
del { text-decoration: line-through; color: #666; }
|
||||
|
||||
ul, ol { margin-bottom: 14px; padding-left: 20px; }
|
||||
li { margin-bottom: 4px; list-style-position: outside; }
|
||||
ul li { list-style-type: disc; }
|
||||
ol li { list-style-type: decimal; }
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #00b96b;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 10px 14px;
|
||||
margin: 14px 0;
|
||||
border-radius: 4px;
|
||||
color: #bbb;
|
||||
font-style: italic;
|
||||
|
||||
p { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
a { color: #00b96b; text-decoration: none; }
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: none;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 14px 0;
|
||||
font-size: 13px;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
|
||||
th, td {
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: rgba(255,255,255,0.05);
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
color: #ff7875;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #161616;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin: 16px 0;
|
||||
border: 1px solid #333;
|
||||
|
||||
code {
|
||||
background: transparent;
|
||||
color: #a6e22e;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import Taro, { useRouter } from '@tarojs/taro'
|
||||
import { View, Text, Input, Textarea, Button, Picker, ScrollView } from '@tarojs/components'
|
||||
import { View, Text, Input, Textarea, Button, Picker, ScrollView, RichText } from '@tarojs/components'
|
||||
import { createTopic, updateTopic, getTopicDetail, uploadMedia } from '../../../api'
|
||||
import { marked } from 'marked'
|
||||
import './create.scss'
|
||||
|
||||
const CreateTopic = () => {
|
||||
@@ -12,6 +13,7 @@ const CreateTopic = () => {
|
||||
const [content, setContent] = useState('')
|
||||
const [categoryIndex, setCategoryIndex] = useState(0)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isPreview, setIsPreview] = useState(false)
|
||||
|
||||
const categories = [
|
||||
{ key: 'discussion', label: '技术讨论' },
|
||||
@@ -25,7 +27,7 @@ const CreateTopic = () => {
|
||||
Taro.showLoading({ title: '加载中...' })
|
||||
try {
|
||||
const res = await getTopicDetail(Number(id))
|
||||
const topic = res.data
|
||||
const topic = res.data || res
|
||||
setTitle(topic.title)
|
||||
setContent(topic.content)
|
||||
const idx = categories.findIndex(c => c.key === topic.category)
|
||||
@@ -64,7 +66,7 @@ const CreateTopic = () => {
|
||||
|
||||
let url = uploadRes.file
|
||||
if (url && !url.startsWith('http')) {
|
||||
const BASE_URL = process.env.TARO_APP_API_URL || 'https://market.quant-speed.com/api'
|
||||
const BASE_URL = (typeof process !== 'undefined' && process.env && process.env.TARO_APP_API_URL) || 'https://market.quant-speed.com/api'
|
||||
const host = BASE_URL.replace(/\/api\/?$/, '')
|
||||
if (!url.startsWith('/')) url = '/' + url
|
||||
url = `${host}${url}`
|
||||
@@ -124,6 +126,24 @@ const CreateTopic = () => {
|
||||
|
||||
return (
|
||||
<ScrollView scrollY className='create-topic-page'>
|
||||
<View className='content-wrapper'>
|
||||
<View className='header-actions'>
|
||||
<View
|
||||
className={`action-item ${!isPreview ? 'active' : ''}`}
|
||||
onClick={() => setIsPreview(false)}
|
||||
>
|
||||
编辑
|
||||
</View>
|
||||
<View
|
||||
className={`action-item ${isPreview ? 'active' : ''}`}
|
||||
onClick={() => setIsPreview(true)}
|
||||
>
|
||||
预览
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{!isPreview ? (
|
||||
<>
|
||||
<View className='form-item'>
|
||||
<Text className='label'>板块</Text>
|
||||
<Picker range={categories} rangeKey='label' value={categoryIndex} onChange={handleCategoryChange}>
|
||||
@@ -157,6 +177,15 @@ const CreateTopic = () => {
|
||||
+ 插入图片/视频
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<View className='preview-container'>
|
||||
<View className='preview-title'>{title || '无标题'}</View>
|
||||
<View className='markdown-body'>
|
||||
<RichText nodes={(marked.parse(content || '无内容') as string).replace(/<img/g, '<img style="max-width:100%;border-radius:8px;"')} />
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className={`submit-btn ${loading ? 'disabled' : ''}`}
|
||||
@@ -165,6 +194,7 @@ const CreateTopic = () => {
|
||||
>
|
||||
{loading ? (id ? '更新中...' : '发布中...') : (id ? '更新话题' : '发布话题')}
|
||||
</Button>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,102 +1,216 @@
|
||||
.forum-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #000;
|
||||
padding-bottom: 80px;
|
||||
background-color: #121212;
|
||||
padding-bottom: 90px;
|
||||
color: #fff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
|
||||
.topic-card {
|
||||
background: rgba(20,20,20,0.8);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
background: #1e1e1e;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
padding: 24px 20px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||||
|
||||
.header {
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 32px; /* Increased from 28px */
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
font-size: 14px; /* Increased from 13px */
|
||||
|
||||
.author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 10px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
padding: 6px 12px 6px 6px;
|
||||
border-radius: 20px;
|
||||
|
||||
.avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 32px; /* Larger avatar */
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.verified {
|
||||
color: #00b96b;
|
||||
font-size: 16px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #ddd;
|
||||
font-size: 20px; /* Increased from 18px */
|
||||
line-height: 1.9; /* Improved readability */
|
||||
color: #e0e0e0;
|
||||
letter-spacing: 0.3px;
|
||||
|
||||
image {
|
||||
max-width: 100%;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
border-radius: 12px;
|
||||
margin: 24px 0;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Markdown styling enhancements */
|
||||
h1, h2, h3, h4, h5, h6 { margin-top: 32px; margin-bottom: 20px; color: #fff; font-weight: 700; line-height: 1.4; }
|
||||
h1 { font-size: 28px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 12px; }
|
||||
h2 { font-size: 24px; border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 8px; }
|
||||
h3 { font-size: 22px; }
|
||||
h4 { font-size: 20px; }
|
||||
h5 { font-size: 18px; color: #ddd; }
|
||||
|
||||
p { margin-bottom: 24px; }
|
||||
|
||||
strong { font-weight: 800; color: #fff; }
|
||||
em { font-style: italic; color: #aaa; }
|
||||
del { text-decoration: line-through; color: #666; }
|
||||
|
||||
ul, ol { margin-bottom: 24px; padding-left: 20px; }
|
||||
li { margin-bottom: 8px; list-style-position: outside; }
|
||||
ul li { list-style-type: disc; }
|
||||
ol li { list-style-type: decimal; }
|
||||
|
||||
/* Task lists */
|
||||
li input[type="checkbox"] { margin-right: 8px; }
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #00b96b;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 16px 20px;
|
||||
margin: 24px 0;
|
||||
border-radius: 4px;
|
||||
color: #bbb;
|
||||
font-size: 18px;
|
||||
font-style: italic;
|
||||
|
||||
p { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
a { color: #00b96b; text-decoration: none; border-bottom: 1px solid transparent; transition: border-color 0.2s; }
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: none;
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 24px 0;
|
||||
font-size: 16px;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
|
||||
th, td {
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: rgba(255,255,255,0.05);
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background: rgba(255,255,255,0.02);
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
color: #ff7875;
|
||||
font-size: 16px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #161616;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
overflow-x: auto;
|
||||
margin: 24px 0;
|
||||
border: 1px solid #333;
|
||||
box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
|
||||
|
||||
code {
|
||||
background: transparent;
|
||||
color: #a6e22e;
|
||||
padding: 0;
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-list {
|
||||
margin-top: 20px;
|
||||
margin-top: 24px;
|
||||
|
||||
.media-item {
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.replies-section {
|
||||
padding: 0 15px;
|
||||
padding: 0 16px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 5px;
|
||||
border-left: 3px solid #00b96b;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 12px;
|
||||
border-left: 4px solid #00b96b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reply-card {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
background: #1e1e1e;
|
||||
border-radius: 12px;
|
||||
padding: 24px; /* Increased padding */
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 16px; /* Increased gap */
|
||||
border: 1px solid rgba(255,255,255,0.03);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
|
||||
.avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: 44px; /* Larger avatar */
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.reply-main {
|
||||
@@ -105,29 +219,33 @@
|
||||
.reply-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
align-items: baseline;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.nickname {
|
||||
font-weight: bold;
|
||||
color: #aaa;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #ddd;
|
||||
font-size: 16px; /* Larger nickname */
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-content {
|
||||
font-size: 14px;
|
||||
color: #eee;
|
||||
line-height: 1.5;
|
||||
font-size: 17px; /* Larger reply text */
|
||||
color: #ccc;
|
||||
line-height: 1.8;
|
||||
|
||||
image {
|
||||
max-width: 100%;
|
||||
border-radius: 4px;
|
||||
margin-top: 5px;
|
||||
border-radius: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,36 +257,57 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #1a1a1a;
|
||||
padding: 10px 15px;
|
||||
border-top: 1px solid #333;
|
||||
background: rgba(20, 20, 20, 0.95); /* More opaque */
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
padding: 16px 20px;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 16px;
|
||||
z-index: 100;
|
||||
box-shadow: 0 -4px 20px rgba(0,0,0,0.3);
|
||||
|
||||
.input-wrapper {
|
||||
flex: 1;
|
||||
background: #333;
|
||||
border-radius: 20px;
|
||||
padding: 8px 15px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 26px;
|
||||
padding: 14px 20px; /* Larger input area */
|
||||
transition: all 0.3s;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:focus-within {
|
||||
background: rgba(255,255,255,0.15);
|
||||
border-color: rgba(0, 185, 107, 0.5);
|
||||
}
|
||||
|
||||
input {
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
font-size: 18px; /* Larger text */
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0 10px;
|
||||
width: 48px; /* Larger touch target */
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:active {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
color: #00b96b;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ const ForumDetail = () => {
|
||||
const fetchDetail = async () => {
|
||||
try {
|
||||
const res = await getTopicDetail(Number(id))
|
||||
setTopic(res.data)
|
||||
const topicData = res.data || res
|
||||
setTopic(topicData)
|
||||
|
||||
// Parse markdown
|
||||
if (res.data.content) {
|
||||
const html = marked.parse(res.data.content)
|
||||
if (topicData.content) {
|
||||
const html = marked.parse(topicData.content)
|
||||
// Basic fix for images to fit screen
|
||||
const styledHtml = (html as string).replace(/<img/g, '<img style="max-width:100%;border-radius:8px;"')
|
||||
setHtmlContent(styledHtml)
|
||||
@@ -87,7 +88,7 @@ const ForumDetail = () => {
|
||||
let url = uploadRes.file
|
||||
// Ensure full URL if needed (backend usually returns relative or absolute)
|
||||
if (url && !url.startsWith('http')) {
|
||||
const BASE_URL = process.env.TARO_APP_API_URL || 'https://market.quant-speed.com/api'
|
||||
const BASE_URL = (typeof process !== 'undefined' && process.env && process.env.TARO_APP_API_URL) || 'https://market.quant-speed.com/api'
|
||||
const host = BASE_URL.replace(/\/api\/?$/, '')
|
||||
if (!url.startsWith('/')) url = '/' + url
|
||||
url = `${host}${url}`
|
||||
@@ -151,18 +152,21 @@ const ForumDetail = () => {
|
||||
<View className='meta'>
|
||||
<View className='author'>
|
||||
<Image className='avatar' src={topic.author_info?.avatar_url || 'https://via.placeholder.com/30'} />
|
||||
<Text>{topic.author_info?.nickname}</Text>
|
||||
{topic.is_verified_owner && <Text className='verified'>✓</Text>}
|
||||
<Text style={{fontWeight: 600, color: '#ccc'}}>{topic.author_info?.nickname}</Text>
|
||||
{topic.is_verified_owner && <AtIcon value='check-circle' size='14' color='#00b96b' />}
|
||||
</View>
|
||||
<Text>•</Text>
|
||||
<Text>{new Date(topic.created_at).toLocaleDateString()}</Text>
|
||||
<Text>•</Text>
|
||||
<Text>{topic.view_count} 阅读</Text>
|
||||
<View style={{display: 'flex', alignItems: 'center'}}>
|
||||
<AtIcon value='eye' size='14' color='#666' style={{marginRight: 4}} />
|
||||
<Text>{topic.view_count}</Text>
|
||||
</View>
|
||||
|
||||
{userInfo && topic.author === userInfo.id && (
|
||||
<View onClick={handleEdit} style={{display: 'flex', alignItems: 'center', marginLeft: 'auto', padding: '4px 8px', background: 'rgba(255,255,255,0.1)', borderRadius: 4}}>
|
||||
<View onClick={handleEdit} style={{display: 'flex', alignItems: 'center', marginLeft: 'auto', padding: '6px 12px', background: 'rgba(0, 185, 107, 0.15)', borderRadius: 20}}>
|
||||
<AtIcon value='edit' size='14' color='#00b96b' />
|
||||
<Text style={{fontSize: 12, color: '#00b96b', marginLeft: 4}}>编辑</Text>
|
||||
<Text style={{fontSize: 12, color: '#00b96b', marginLeft: 4, fontWeight: 600}}>编辑</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
@@ -191,8 +195,11 @@ const ForumDetail = () => {
|
||||
<Image className='avatar' src={reply.author_info?.avatar_url || 'https://via.placeholder.com/30'} />
|
||||
<View className='reply-main'>
|
||||
<View className='reply-header'>
|
||||
<View style={{display: 'flex', flexDirection: 'column'}}>
|
||||
<Text className='nickname'>{reply.author_info?.nickname}</Text>
|
||||
<Text className='time'>#{idx + 1} • {new Date(reply.created_at).toLocaleDateString()}</Text>
|
||||
<Text style={{fontSize: 10, color: '#666', marginTop: 2}}>#{idx + 1} • {new Date(reply.created_at).toLocaleDateString()}</Text>
|
||||
</View>
|
||||
<AtIcon value='message' size='14' color='#444' />
|
||||
</View>
|
||||
<View className='reply-content'>
|
||||
{/* Simple markdown render for replies or just text if complex */}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
const BASE_URL = process.env.TARO_APP_API_URL || 'https://market.quant-speed.com/api'
|
||||
const BASE_URL = (typeof process !== 'undefined' && process.env && process.env.TARO_APP_API_URL) || 'https://market.quant-speed.com/api'
|
||||
|
||||
export const request = async (options: Taro.request.Option) => {
|
||||
const token = Taro.getStorageSync('token')
|
||||
|
||||
Reference in New Issue
Block a user