Skip to content

Commit 1617252

Browse files
committed
feat: add community page (topics & booths)
1 parent fda19f7 commit 1617252

File tree

3 files changed

+466
-0
lines changed

3 files changed

+466
-0
lines changed

components/Community.vue

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
<script setup lang="ts">
2+
import { data } from '#loaders/community.data'
3+
import { ref } from 'vue'
4+
5+
const { communities, community_topics, community_booths } = data
6+
7+
// 控制展開的社群卡片
8+
const expanded = ref<string | null>(null)
9+
10+
function toggleExpand(id: string) {
11+
expanded.value = expanded.value === id ? null : id
12+
}
13+
14+
// 處理 Google Drive 圖片
15+
function getImageUrl(image: string) {
16+
if (image.includes('drive.google.com')) {
17+
const match = image.match(/\/d\/([^/]+)\//)
18+
if (match) {
19+
return `https://drive.google.com/uc?export=view&id=${match[1]}`
20+
}
21+
}
22+
return image
23+
}
24+
25+
// 根據社群 ID 取得所屬的 topics & booths
26+
function getTopics(communityId: string): any[] {
27+
return community_topics[communityId] || []
28+
}
29+
30+
function getBooths(communityId: string): any[] {
31+
return community_booths[communityId] || []
32+
}
33+
34+
const showTagDetail = ref(false)
35+
const selectedTag = ref<any>(null)
36+
const selectedTagType = ref<'topic' | 'booth' | null>(null)
37+
38+
function openTagDetail(tag: any, type: 'topic' | 'booth') {
39+
selectedTag.value = tag
40+
selectedTagType.value = type
41+
showTagDetail.value = true
42+
}
43+
44+
function closeTagDetail() {
45+
showTagDetail.value = false
46+
selectedTag.value = null
47+
selectedTagType.value = null
48+
}
49+
</script>
50+
51+
<template>
52+
<div class="community-list">
53+
<div
54+
v-for="community in communities"
55+
:key="community.id"
56+
class="community-card"
57+
@click="toggleExpand(community.id)"
58+
>
59+
<div class="card-content">
60+
<div class="community-image-wrapper">
61+
<img
62+
:alt="community['name:zh-TW']"
63+
class="community-image"
64+
:src="getImageUrl(community.image)"
65+
>
66+
</div>
67+
68+
<div class="community-text">
69+
<h1 class="community-name">
70+
{{ community['name:zh-TW'] }}
71+
</h1>
72+
73+
<a
74+
class="community-link"
75+
:href="community.link"
76+
rel="noopener noreferrer"
77+
target="_blank"
78+
@click.stop
79+
>
80+
🔗 官方網站
81+
</a>
82+
83+
<div class="community-tags">
84+
<div class="topics-wrapper">
85+
<span
86+
v-for="topic in getTopics(community.id)"
87+
:key="topic"
88+
class="tag topic"
89+
@click.stop="openTagDetail(topic, 'topic')"
90+
>
91+
#{{ topic['name:zh-TW'] }}
92+
</span>
93+
</div>
94+
95+
<div class="booths-wrapper">
96+
<span
97+
v-for="booth in getBooths(community.id)"
98+
:key="booth"
99+
class="tag booth"
100+
@click.stop="openTagDetail(booth, 'booth')"
101+
>
102+
#{{ booth['name:zh-TW'] }}
103+
<!--
104+
<small class="booth-meta">(社群:{{ booth['community'] }})</small>
105+
-->
106+
</span>
107+
</div>
108+
</div>
109+
110+
<transition name="fade">
111+
<div
112+
v-if="expanded === community.id"
113+
class="community-info"
114+
>
115+
<p>{{ community['intro:zh-TW'] }}</p>
116+
</div>
117+
</transition>
118+
</div>
119+
</div>
120+
</div>
121+
</div>
122+
123+
<div
124+
v-if="showTagDetail"
125+
class="modal-backdrop"
126+
@click.self="closeTagDetail"
127+
>
128+
<div class="modal-content">
129+
<h3>{{ selectedTagType === 'topic' ? '議程主題與活動' : '參展攤位' }}</h3>
130+
<ul class="detail-list">
131+
<li><strong>名稱:</strong>{{ selectedTag['name:zh-TW'] }}</li>
132+
<li><strong>簡介:</strong>{{ selectedTag['intro:zh-TW'] }}</li>
133+
<li v-if="selectedTag.link">
134+
<strong>連結:</strong>
135+
<a
136+
:href="selectedTag.link"
137+
target="_blank"
138+
>{{ selectedTag.link }}</a>
139+
</li>
140+
<li v-if="selectedTag.community">
141+
<strong>社群:</strong>{{ selectedTag.community }}
142+
</li>
143+
<li v-if="selectedTag.room">
144+
<strong>展區:</strong>{{ selectedTag.room }}
145+
</li>
146+
<li v-if="selectedTag.trackroom">
147+
<strong>軌:</strong>{{ selectedTag.trackroom }}
148+
</li>
149+
</ul>
150+
<button
151+
class="modal-close"
152+
@click="closeTagDetail"
153+
>
154+
關閉
155+
</button>
156+
</div>
157+
</div>
158+
</template>
159+
160+
<style scoped>
161+
.community-list {
162+
padding: 20px;
163+
display: grid;
164+
gap: 24px;
165+
}
166+
167+
.community-card {
168+
background: var(--vp-c-bg);
169+
border: 1px solid var(--vp-c-border);
170+
border-radius: 10px;
171+
padding: 16px;
172+
transition: transform 0.2s ease;
173+
cursor: pointer;
174+
display: flex;
175+
align-items: flex-start;
176+
}
177+
178+
.community-card:hover {
179+
transform: translateY(-5px);
180+
}
181+
182+
.card-content {
183+
display: flex;
184+
gap: 16px;
185+
width: 100%;
186+
}
187+
188+
.community-image-wrapper {
189+
flex: 1;
190+
max-width: 33%;
191+
aspect-ratio: 3 / 2;
192+
overflow: hidden;
193+
display: flex;
194+
justify-content: center;
195+
align-items: center;
196+
}
197+
198+
.community-image {
199+
width: 100%;
200+
height: auto;
201+
object-fit: cover;
202+
border-radius: 6px;
203+
}
204+
205+
.community-text {
206+
flex: 2;
207+
}
208+
209+
.community-name {
210+
font-size: 1.2rem;
211+
font-weight: bold;
212+
color: var(--vp-c-text-1);
213+
}
214+
215+
.community-link {
216+
display: inline-block;
217+
font-size: 0.9rem;
218+
margin-bottom: 10px;
219+
padding: 4px 8px;
220+
border: 1px solid var(--vp-c-brand);
221+
border-radius: 5px;
222+
color: var(--vp-c-brand);
223+
text-decoration: none;
224+
}
225+
226+
.community-link:hover {
227+
background-color: var(--vp-c-brand);
228+
color: white;
229+
}
230+
231+
.community-info {
232+
font-size: 0.9rem;
233+
color: var(--vp-c-text-2);
234+
background: var(--vp-c-bg-soft);
235+
padding: 10px;
236+
border-radius: 6px;
237+
margin-top: 8px;
238+
}
239+
240+
.fade-enter-active,
241+
.fade-leave-active {
242+
transition: opacity 0.3s;
243+
}
244+
245+
.fade-enter-from,
246+
.fade-leave-to {
247+
opacity: 0;
248+
}
249+
250+
@media (max-width: 768px) {
251+
.card-content {
252+
flex-direction: column;
253+
}
254+
255+
.community-image-wrapper {
256+
max-width: 100%;
257+
margin-bottom: 12px;
258+
}
259+
260+
.community-text {
261+
flex-direction: column;
262+
align-items: center;
263+
}
264+
}
265+
266+
.community-tags {
267+
margin-top: 12px;
268+
display: flex;
269+
flex-direction: column;
270+
gap: 6px;
271+
}
272+
273+
.topics-wrapper,
274+
.booths-wrapper {
275+
display: flex;
276+
flex-wrap: wrap;
277+
gap: 8px;
278+
}
279+
280+
.tag {
281+
font-size: 0.85rem;
282+
padding: 4px 10px;
283+
border-radius: 999px;
284+
display: inline-flex;
285+
align-items: center;
286+
white-space: nowrap;
287+
font-weight: 500;
288+
}
289+
290+
.topic {
291+
background-color: #fff4cc;
292+
color: #664d03;
293+
border: 1px solid #ffe58f;
294+
}
295+
296+
.booth {
297+
background-color: #ccf2f1;
298+
color: #00504e;
299+
border: 1px solid #87e0dc;
300+
}
301+
302+
.detail-list {
303+
margin: 10px 0;
304+
padding-left: 1rem;
305+
list-style-type: disc;
306+
color: var(--vp-c-text-2);
307+
font-size: 0.9rem;
308+
}
309+
310+
.detail-list li {
311+
margin-bottom: 6px;
312+
line-height: 1.4;
313+
word-break: break-word;
314+
}
315+
316+
.detail-list a {
317+
color: var(--vp-c-brand);
318+
text-decoration: underline;
319+
}
320+
321+
.modal-backdrop {
322+
position: fixed;
323+
inset: 0;
324+
background-color: rgba(0, 0, 0, 0.5);
325+
display: flex;
326+
justify-content: center;
327+
align-items: center;
328+
z-index: 1000;
329+
}
330+
331+
.modal-content {
332+
background: white;
333+
padding: 20px;
334+
border-radius: 10px;
335+
width: 90%;
336+
max-width: 500px;
337+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
338+
position: relative;
339+
}
340+
341+
.modal-content h3 {
342+
margin-top: 0;
343+
font-size: 1.2rem;
344+
}
345+
346+
.modal-close {
347+
margin-top: 16px;
348+
padding: 6px 12px;
349+
background-color: var(--vp-c-brand);
350+
color: white;
351+
border: none;
352+
border-radius: 6px;
353+
cursor: pointer;
354+
}
355+
</style>

content/zh_tw/community/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: 社群夥伴
3+
---
4+
5+
# COSCUP Taiwan 2025 社群夥伴 {style="text-align:center"}
6+
7+
<Community />

0 commit comments

Comments
 (0)