Skip to content

Commit 5d94ea3

Browse files
committed
style: footer
1 parent e40e805 commit 5d94ea3

File tree

4 files changed

+187
-51
lines changed

4 files changed

+187
-51
lines changed

src/components/Footer.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,17 @@ export default function Footer() {
160160
<SiWechat className={styles.socialIcon} />
161161
</button>
162162
</div>
163+
164+
{/* Volunteer button */}
165+
<div className={styles.volunteerSection}>
166+
<Link
167+
href="#"
168+
className={styles.volunteerButton}
169+
aria-label="成为志愿者"
170+
>
171+
成为志愿者
172+
</Link>
173+
</div>
163174
</div>
164175
</div>
165176

src/components/home/partners/Partners.module.css

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,20 @@
8888
margin-bottom: 5rem;
8989
}
9090

91+
/* Partners Carousel Container */
92+
.partnersCarouselContainer {
93+
width: 100%;
94+
margin-top: 2rem;
95+
position: relative;
96+
display: flex;
97+
flex-direction: column;
98+
gap: 1.5rem;
99+
}
100+
91101
/* Partners Carousel */
92102
.partnersCarousel {
93103
width: 100%;
94104
overflow: hidden;
95-
margin-top: 2rem;
96105
position: relative;
97106
border-radius: 16px;
98107
background: rgba(255, 255, 255, 0.05);
@@ -262,6 +271,10 @@
262271
font-size: 2rem;
263272
}
264273

274+
.partnersCarouselContainer {
275+
gap: 1.25rem;
276+
}
277+
265278
.partnersCarousel {
266279
padding: 0.75rem 0;
267280
}
@@ -294,6 +307,10 @@
294307
font-size: 1.8rem;
295308
}
296309

310+
.partnersCarouselContainer {
311+
gap: 1rem;
312+
}
313+
297314
.partnersCarousel {
298315
padding: 0.5rem 0;
299316
}

src/components/home/partners/Partners.tsx

Lines changed: 121 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,60 +5,100 @@ import { partnersRawData } from '@/data/partners'
55
import styles from './Partners.module.css'
66

77
export default function PartnersSection() {
8-
const scrollRef = useRef<HTMLDivElement>(null)
8+
const scrollRef1 = useRef<HTMLDivElement>(null)
9+
const scrollRef2 = useRef<HTMLDivElement>(null)
910

10-
// 只显示有logo的合作伙伴,不区分时间和等级
11-
const validPartners = partnersRawData.filter(
12-
partner => partner.logo &&
13-
partner.logo !== '/logo.png' &&
14-
partner.logo.trim() !== ''
15-
)
11+
// 只显示有logo的合作伙伴,不区分时间和等级,并根据title去重
12+
const validPartners = partnersRawData
13+
.filter(
14+
partner => partner.logo &&
15+
partner.logo !== '/logo.png' &&
16+
partner.logo.trim() !== ''
17+
)
18+
.reduce<typeof partnersRawData>((acc, partner) => {
19+
// 检查是否已存在相同title的合作伙伴
20+
const existingIndex = acc.findIndex(existing => existing.title === partner.title)
21+
if (existingIndex === -1) {
22+
// 如果不存在,直接添加
23+
acc.push(partner)
24+
}
25+
return acc
26+
}, [])
1627

17-
// 将合作伙伴数组复制一份以实现无缝轮播
18-
const duplicatedPartners = [...validPartners, ...validPartners]
28+
// 将合作伙伴数组分成两组
29+
const midIndex = Math.ceil(validPartners.length / 2)
30+
const firstRowPartners = [...validPartners.slice(0, midIndex), ...validPartners.slice(0, midIndex)]
31+
const secondRowPartners = [...validPartners.slice(midIndex), ...validPartners.slice(midIndex)]
1932

2033
useEffect(() => {
21-
let animationFrame: number
22-
const scrollContainer = scrollRef.current
34+
let animationFrame1: number
35+
let animationFrame2: number
36+
const scrollContainer1 = scrollRef1.current
37+
const scrollContainer2 = scrollRef2.current
2338

24-
const scroll = () => {
25-
if (scrollContainer) {
26-
scrollContainer.scrollLeft += 1
39+
const scroll1 = () => {
40+
if (scrollContainer1) {
41+
scrollContainer1.scrollLeft += 1
2742

2843
// 当滚动到一半时重置到开始位置,实现无缝循环
29-
if (scrollContainer.scrollLeft >= scrollContainer.scrollWidth / 2) {
30-
scrollContainer.scrollLeft = 0
44+
if (scrollContainer1.scrollLeft >= scrollContainer1.scrollWidth / 2) {
45+
scrollContainer1.scrollLeft = 0
46+
}
47+
}
48+
animationFrame1 = requestAnimationFrame(scroll1)
49+
}
50+
51+
const scroll2 = () => {
52+
if (scrollContainer2) {
53+
scrollContainer2.scrollLeft -= 1
54+
55+
// 当滚动到开始位置时重置到一半位置,实现反向无缝循环
56+
if (scrollContainer2.scrollLeft <= 0) {
57+
scrollContainer2.scrollLeft = scrollContainer2.scrollWidth / 2
3158
}
3259
}
33-
animationFrame = requestAnimationFrame(scroll)
60+
animationFrame2 = requestAnimationFrame(scroll2)
3461
}
3562

3663
// 开始自动滚动
3764
const startScrolling = () => {
38-
animationFrame = requestAnimationFrame(scroll)
65+
animationFrame1 = requestAnimationFrame(scroll1)
66+
animationFrame2 = requestAnimationFrame(scroll2)
3967
}
4068

4169
// 停止自动滚动
4270
const stopScrolling = () => {
43-
cancelAnimationFrame(animationFrame)
71+
cancelAnimationFrame(animationFrame1)
72+
cancelAnimationFrame(animationFrame2)
4473
}
4574

4675
// 添加鼠标事件监听器
47-
if (scrollContainer) {
48-
scrollContainer.addEventListener('mouseenter', stopScrolling)
49-
scrollContainer.addEventListener('mouseleave', startScrolling)
76+
const containers = [scrollContainer1, scrollContainer2]
77+
containers.forEach(container => {
78+
if (container) {
79+
container.addEventListener('mouseenter', stopScrolling)
80+
container.addEventListener('mouseleave', startScrolling)
81+
}
82+
})
83+
84+
// 初始化第二行的滚动位置
85+
if (scrollContainer2) {
86+
scrollContainer2.scrollLeft = scrollContainer2.scrollWidth / 2
5087
}
5188

5289
// 启动轮播
5390
startScrolling()
5491

5592
// 清理函数
5693
return () => {
57-
cancelAnimationFrame(animationFrame)
58-
if (scrollContainer) {
59-
scrollContainer.removeEventListener('mouseenter', stopScrolling)
60-
scrollContainer.removeEventListener('mouseleave', startScrolling)
61-
}
94+
cancelAnimationFrame(animationFrame1)
95+
cancelAnimationFrame(animationFrame2)
96+
containers.forEach(container => {
97+
if (container) {
98+
container.removeEventListener('mouseenter', stopScrolling)
99+
container.removeEventListener('mouseleave', startScrolling)
100+
}
101+
})
62102
}
63103
}, [])
64104

@@ -72,29 +112,60 @@ export default function PartnersSection() {
72112
<div className={styles.blockDivider}></div>
73113
</div>
74114

75-
<div className={styles.partnersCarousel}>
76-
<div
77-
ref={scrollRef}
78-
className={styles.partnersScroll}
79-
>
80-
<div className={styles.partnersTrack}>
81-
{duplicatedPartners.map((partner, index) => (
82-
<Link
83-
key={`${partner.organization}-${index}`}
84-
href="/partners"
85-
className={styles.partnerCard}
86-
>
87-
<div className={styles.partnerLogoWrapper}>
88-
<Image
89-
src={partner.logo}
90-
alt={partner.title}
91-
width={120}
92-
height={80}
93-
className={styles.partnerLogo}
94-
/>
95-
</div>
96-
</Link>
97-
))}
115+
<div className={styles.partnersCarouselContainer}>
116+
{/* 第一行 - 向右滚动 */}
117+
<div className={styles.partnersCarousel}>
118+
<div
119+
ref={scrollRef1}
120+
className={styles.partnersScroll}
121+
>
122+
<div className={styles.partnersTrack}>
123+
{firstRowPartners.map((partner, index) => (
124+
<Link
125+
key={`row1-${partner.organization}-${index}`}
126+
href="/partners"
127+
className={styles.partnerCard}
128+
>
129+
<div className={styles.partnerLogoWrapper}>
130+
<Image
131+
src={partner.logo}
132+
alt={partner.title}
133+
width={120}
134+
height={80}
135+
className={styles.partnerLogo}
136+
/>
137+
</div>
138+
</Link>
139+
))}
140+
</div>
141+
</div>
142+
</div>
143+
144+
{/* 第二行 - 向左滚动 */}
145+
<div className={styles.partnersCarousel}>
146+
<div
147+
ref={scrollRef2}
148+
className={styles.partnersScroll}
149+
>
150+
<div className={styles.partnersTrack}>
151+
{secondRowPartners.map((partner, index) => (
152+
<Link
153+
key={`row2-${partner.organization}-${index}`}
154+
href="/partners"
155+
className={styles.partnerCard}
156+
>
157+
<div className={styles.partnerLogoWrapper}>
158+
<Image
159+
src={partner.logo}
160+
alt={partner.title}
161+
width={120}
162+
height={80}
163+
className={styles.partnerLogo}
164+
/>
165+
</div>
166+
</Link>
167+
))}
168+
</div>
98169
</div>
99170
</div>
100171
</div>

src/styles/Footer.module.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,33 @@
8484
align-items: center;
8585
}
8686

87+
.volunteerSection {
88+
margin-top: 1.5rem;
89+
}
90+
91+
.volunteerButton {
92+
display: inline-flex;
93+
align-items: center;
94+
justify-content: center;
95+
background: transparent;
96+
color: white;
97+
text-decoration: none;
98+
padding: 0.75rem 1.5rem;
99+
border-radius: 0.5rem;
100+
font-size: 0.875rem;
101+
font-weight: 600;
102+
transition: all 0.3s ease;
103+
border: 1px solid rgba(255, 255, 255, 0.8);
104+
cursor: pointer;
105+
}
106+
107+
.volunteerButton:hover {
108+
background: rgba(255, 255, 255, 0.1);
109+
border-color: white;
110+
transform: translateY(-1px);
111+
color: white;
112+
}
113+
87114
/* Right section with navigation menu */
88115
.footerRight {
89116
flex: 2;
@@ -173,6 +200,11 @@
173200
justify-content: center;
174201
}
175202

203+
.volunteerSection {
204+
text-align: center;
205+
margin-top: 1rem;
206+
}
207+
176208
.footerRight {
177209
grid-template-columns: repeat(2, 1fr);
178210
gap: 1.5rem;
@@ -204,6 +236,11 @@
204236
.footerLogo {
205237
max-width: 150px;
206238
}
239+
240+
.volunteerButton {
241+
padding: 0.625rem 1.25rem;
242+
font-size: 0.8rem;
243+
}
207244
}
208245

209246

0 commit comments

Comments
 (0)