This commit is contained in:
@@ -173,6 +173,60 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activityTitle {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1.3;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
font-size: 22px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.metaInfo {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.metaItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
font-size: 13px;
|
||||||
|
gap: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusWrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerGradient {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
background: linear-gradient(to top, #1f1f1f, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -188,6 +242,11 @@
|
|||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
height: 40vh;
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailImage {
|
.detailImage {
|
||||||
@@ -202,6 +261,11 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
padding: 0 var(--spacing-lg) 100px; /* Bottom padding for fixed footer */
|
padding: 0 var(--spacing-lg) 100px; /* Bottom padding for fixed footer */
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 0 var(--spacing-md) 80px;
|
||||||
|
margin-top: -40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoCard {
|
.infoCard {
|
||||||
@@ -211,6 +275,11 @@
|
|||||||
box-shadow: var(--box-shadow-base);
|
box-shadow: var(--box-shadow-base);
|
||||||
margin-bottom: var(--spacing-lg);
|
margin-bottom: var(--spacing-lg);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.richText {
|
.richText {
|
||||||
@@ -218,6 +287,10 @@
|
|||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: var(--border-radius-base);
|
border-radius: var(--border-radius-base);
|
||||||
@@ -227,6 +300,11 @@
|
|||||||
h1, h2, h3 {
|
h1, h2, h3 {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin-top: var(--spacing-lg);
|
margin-top: var(--spacing-lg);
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
font-size: 1.2em; /* slightly smaller headings */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const ActivityDetail = () => {
|
|||||||
const headerColor = useTransform(scrollY, [0, 60], ['rgba(255,255,255,1)', 'rgba(0,0,0,0.85)']);
|
const headerColor = useTransform(scrollY, [0, 60], ['rgba(255,255,255,1)', 'rgba(0,0,0,0.85)']);
|
||||||
const titleOpacity = useTransform(scrollY, [100, 200], [0, 1]);
|
const titleOpacity = useTransform(scrollY, [100, 200], [0, 1]);
|
||||||
|
|
||||||
const { data: activity, isLoading, error, refetch } = useQuery({
|
const { data: activity, isLoading, error } = useQuery({
|
||||||
queryKey: ['activity', id],
|
queryKey: ['activity', id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
@@ -140,7 +140,7 @@ const ActivityDetail = () => {
|
|||||||
|
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// ignore error during polling
|
// ignore error during polling
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
@@ -188,7 +188,7 @@ const ActivityDetail = () => {
|
|||||||
text: '来看看这个精彩活动!',
|
text: '来看看这个精彩活动!',
|
||||||
url: url
|
url: url
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.log('Share canceled');
|
console.log('Share canceled');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -256,6 +256,23 @@ const ActivityDetail = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!activity) {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: 40, background: '#1f1f1f', minHeight: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<Result
|
||||||
|
status="404"
|
||||||
|
title="活动未找到"
|
||||||
|
subTitle="抱歉,该活动可能已被删除或不存在。"
|
||||||
|
extra={[
|
||||||
|
<Button type="primary" key="back" onClick={() => navigate(-1)}>
|
||||||
|
返回列表
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const cleanUrl = (url) => {
|
const cleanUrl = (url) => {
|
||||||
if (!url) return '';
|
if (!url) return '';
|
||||||
return url.replace(/[`\s]/g, '');
|
return url.replace(/[`\s]/g, '');
|
||||||
@@ -313,45 +330,38 @@ const ActivityDetail = () => {
|
|||||||
alt={activity.title}
|
alt={activity.title}
|
||||||
className={styles.detailImage}
|
className={styles.detailImage}
|
||||||
/>
|
/>
|
||||||
<div style={{
|
<div className={styles.headerGradient} />
|
||||||
position: 'absolute',
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '50%',
|
|
||||||
background: 'linear-gradient(to top, #1f1f1f, transparent)'
|
|
||||||
}} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className={styles.detailContent}>
|
<div className={styles.detailContent}>
|
||||||
<div className={styles.infoCard}>
|
<div className={styles.infoCard}>
|
||||||
<h1 style={{ fontSize: 28, marginBottom: 16, color: '#fff' }}>{activity.title}</h1>
|
<h1 className={styles.activityTitle}>{activity.title}</h1>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 20, marginBottom: 16, color: 'rgba(255,255,255,0.7)' }}>
|
<div className={styles.metaInfo}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div className={styles.metaItem}>
|
||||||
<CalendarOutlined />
|
<CalendarOutlined />
|
||||||
<span>{activity.start_time ? new Date(activity.start_time).toLocaleDateString() : 'TBD'}</span>
|
<span>{activity.start_time ? new Date(activity.start_time).toLocaleDateString() : 'TBD'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div className={styles.metaItem}>
|
||||||
<ClockCircleOutlined />
|
<ClockCircleOutlined />
|
||||||
<span>{activity.start_time ? new Date(activity.start_time).toLocaleTimeString() : 'TBD'}</span>
|
<span>{activity.start_time ? new Date(activity.start_time).toLocaleTimeString() : 'TBD'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div className={styles.metaItem}>
|
||||||
<EnvironmentOutlined />
|
<EnvironmentOutlined />
|
||||||
<span>{activity.location || '线上活动'}</span>
|
<span>{activity.location || '线上活动'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div className={styles.metaItem}>
|
||||||
<UserOutlined />
|
<UserOutlined />
|
||||||
<span>{activity.current_signups || 0} / {activity.max_participants} 已报名</span>
|
<span>{activity.current_signups || 0} / {activity.max_participants} 已报名</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div className={styles.metaItem}>
|
||||||
<PayCircleOutlined />
|
<PayCircleOutlined />
|
||||||
<span>{activity.is_paid ? `¥${activity.price}` : '免费'}</span>
|
<span>{activity.is_paid ? `¥${activity.price}` : '免费'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 10 }}>
|
<div className={styles.statusWrapper}>
|
||||||
<span className={styles.statusTag}>
|
<span className={styles.statusTag}>
|
||||||
{activity.status || (new Date() < new Date(activity.start_time) ? '报名中' : '已结束')}
|
{activity.status || (new Date() < new Date(activity.start_time) ? '报名中' : '已结束')}
|
||||||
</span>
|
</span>
|
||||||
@@ -365,7 +375,7 @@ const ActivityDetail = () => {
|
|||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
rehypePlugins={[rehypeRaw]}
|
rehypePlugins={[rehypeRaw]}
|
||||||
components={{
|
components={{
|
||||||
code({node, inline, className, children, ...props}) {
|
code({inline, className, children, ...props}) {
|
||||||
const match = /language-(\w+)/.exec(className || '')
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
return !inline && match ? (
|
return !inline && match ? (
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
|
|||||||
Reference in New Issue
Block a user