|
|
|
@ -1,6 +1,6 @@ |
|
|
|
<script setup lang="ts"> |
|
|
|
import { useI18n } from 'vue-i18n'; |
|
|
|
import { ref, onMounted, computed } from 'vue'; |
|
|
|
import { ref, onMounted } from 'vue'; |
|
|
|
import { Icon } from '@iconify/vue'; |
|
|
|
import { useConfig } from '@/utils/config'; |
|
|
|
import { Swiper, SwiperSlide } from 'swiper/vue'; |
|
|
|
@ -24,7 +24,7 @@ import { |
|
|
|
|
|
|
|
const { getConfigImage } = useConfig(); |
|
|
|
|
|
|
|
const { t } = useI18n(); |
|
|
|
const { t, locale } = useI18n(); |
|
|
|
|
|
|
|
// 社交媒体账号数据 |
|
|
|
const socialMediaAccounts = ref<OfficialMediaItem[]>([]); |
|
|
|
@ -88,6 +88,9 @@ const swiperOptions = { |
|
|
|
// 弹窗相关数据 |
|
|
|
const showDetailModal = ref(false); |
|
|
|
const selectedMessage = ref<MessageItem | null>(null); |
|
|
|
const showVideoModal = ref(false); |
|
|
|
const showCommunityDetailModal = ref(false); |
|
|
|
const selectedCommunity = ref<CommunityItem | null>(null); |
|
|
|
|
|
|
|
// 显示消息详情弹窗 |
|
|
|
const showMessageDetail = (message: MessageItem) => { |
|
|
|
@ -101,9 +104,35 @@ const closeMessageDetail = () => { |
|
|
|
selectedMessage.value = null; |
|
|
|
}; |
|
|
|
|
|
|
|
// 显示视频弹窗 |
|
|
|
const showVideo = (community: CommunityItem) => { |
|
|
|
if (community.video) { |
|
|
|
selectedCommunity.value = community; |
|
|
|
showVideoModal.value = true; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 关闭视频弹窗 |
|
|
|
const closeVideoModal = () => { |
|
|
|
showVideoModal.value = false; |
|
|
|
selectedCommunity.value = null; |
|
|
|
}; |
|
|
|
|
|
|
|
// 显示社区详情弹窗 |
|
|
|
const showCommunityDetail = (community: CommunityItem) => { |
|
|
|
selectedCommunity.value = community; |
|
|
|
showCommunityDetailModal.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
// 关闭社区详情弹窗 |
|
|
|
const closeCommunityDetailModal = () => { |
|
|
|
showCommunityDetailModal.value = false; |
|
|
|
selectedCommunity.value = null; |
|
|
|
}; |
|
|
|
|
|
|
|
// 跳转到公告详情 |
|
|
|
const goToMessage = (id: string) => { |
|
|
|
console.log('查看公告详情:', id); |
|
|
|
console.log(t('community.message.viewDetail'), id); |
|
|
|
// 跳转到公告详情页面实现 |
|
|
|
}; |
|
|
|
|
|
|
|
@ -112,7 +141,7 @@ const formatDate = (dateString: string) => { |
|
|
|
if (!dateString) return ''; |
|
|
|
|
|
|
|
const date = new Date(dateString); |
|
|
|
return new Intl.DateTimeFormat('zh-CN', { |
|
|
|
return new Intl.DateTimeFormat(locale.value === 'zh' ? 'zh-CN' : 'en-US', { |
|
|
|
year: 'numeric', |
|
|
|
month: 'long', |
|
|
|
day: 'numeric' |
|
|
|
@ -127,9 +156,36 @@ const loadSocialMedia = async () => { |
|
|
|
pageSize: 10, |
|
|
|
pageNo: 1 |
|
|
|
}); |
|
|
|
socialMediaAccounts.value = data; |
|
|
|
// Only keep image data from API, use i18n for text |
|
|
|
socialMediaAccounts.value = data.map((item, index) => ({ |
|
|
|
id: item.id || index + 1, |
|
|
|
title: t(`community.social_media.default.item${index + 1}.title`), |
|
|
|
username: t(`community.social_media.default.item${index + 1}.username`), |
|
|
|
description: t(`community.social_media.default.item${index + 1}.description`), |
|
|
|
image: item.image || '/LOGO.png', |
|
|
|
url: '#' |
|
|
|
})); |
|
|
|
} catch (error) { |
|
|
|
console.error('加载社交媒体数据失败:', error); |
|
|
|
console.error(t('common.error.loadSocialMediaFailed'), error); |
|
|
|
// Use default i18n data on error |
|
|
|
socialMediaAccounts.value = [ |
|
|
|
{ |
|
|
|
id: 1, |
|
|
|
title: t('community.social_media.default.twitter.title'), |
|
|
|
username: t('community.social_media.default.twitter.username'), |
|
|
|
description: t('community.social_media.default.twitter.description'), |
|
|
|
image: '/LOGO.png', |
|
|
|
url: '#' |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: 2, |
|
|
|
title: t('community.social_media.default.discord.title'), |
|
|
|
username: t('community.social_media.default.discord.username'), |
|
|
|
description: t('community.social_media.default.discord.description'), |
|
|
|
image: '/LOGO.png', |
|
|
|
url: '#' |
|
|
|
} |
|
|
|
]; |
|
|
|
} finally { |
|
|
|
socialMediaLoading.value = false; |
|
|
|
} |
|
|
|
@ -143,12 +199,21 @@ const loadForum = async () => { |
|
|
|
pageSize: 10, |
|
|
|
pageNo: 1 |
|
|
|
}); |
|
|
|
forumList.value = data; |
|
|
|
// Use original API data for forum content |
|
|
|
forumList.value = data.map((item, index) => ({ |
|
|
|
id: item.id || index + 1, |
|
|
|
title: item.title || t(`community.forum.default.item${index + 1}.title`), |
|
|
|
content: item.content || t(`community.forum.default.item${index + 1}.content`), |
|
|
|
createBy: item.createBy || t(`community.forum.default.item${index + 1}.author`), |
|
|
|
createTime: item.createTime, |
|
|
|
image: item.image || '/LOGO.png', |
|
|
|
likeCount: item.likeCount || 0 |
|
|
|
})); |
|
|
|
|
|
|
|
// 为每个论坛加载对应的评论 |
|
|
|
await Promise.all(data.map(forum => loadComments(forum.id))); |
|
|
|
await Promise.all(forumList.value.map(forum => loadComments(forum.id))); |
|
|
|
} catch (error) { |
|
|
|
console.error('加载论坛数据失败:', error); |
|
|
|
console.error(t('common.error.loadForumFailed'), error); |
|
|
|
} finally { |
|
|
|
forumLoading.value = false; |
|
|
|
} |
|
|
|
@ -161,9 +226,16 @@ const loadComments = async (forumId: string) => { |
|
|
|
const data = await queryCommentsList({ |
|
|
|
forumId |
|
|
|
}); |
|
|
|
commentsMap.value[forumId] = data; |
|
|
|
// Use original API data for comments |
|
|
|
commentsMap.value[forumId] = data.map((item, index) => ({ |
|
|
|
id: item.id || index + 1, |
|
|
|
content: item.content || t(`community.forum.default.comment${index + 1}.content`), |
|
|
|
createBy: item.createBy || t(`community.forum.default.comment${index + 1}.author`), |
|
|
|
createTime: item.createTime, |
|
|
|
forumId: item.forumId |
|
|
|
})); |
|
|
|
} catch (error) { |
|
|
|
console.error(`加载论坛${forumId}的评论数据失败:`, error); |
|
|
|
console.error(t('common.error.loadCommentsFailed', { forumId }), error); |
|
|
|
} finally { |
|
|
|
commentsLoading.value = false; |
|
|
|
} |
|
|
|
@ -177,9 +249,17 @@ const loadMessages = async () => { |
|
|
|
pageSize: 10, |
|
|
|
pageNo: 1 |
|
|
|
}); |
|
|
|
messageList.value = data; |
|
|
|
// Only keep image data from API, use i18n for text |
|
|
|
messageList.value = data.map((item, index) => ({ |
|
|
|
id: item.id || index + 1, |
|
|
|
title: t(`community.announcements.default.item${index + 1}.title`), |
|
|
|
content: t(`community.announcements.default.item${index + 1}.content`), |
|
|
|
description: t(`community.announcements.default.item${index + 1}.description`), |
|
|
|
createTime: item.createTime, |
|
|
|
image: item.image |
|
|
|
})); |
|
|
|
} catch (error) { |
|
|
|
console.error('加载信息公示数据失败:', error); |
|
|
|
console.error(t('common.error.loadMessagesFailed'), error); |
|
|
|
} finally { |
|
|
|
messageLoading.value = false; |
|
|
|
} |
|
|
|
@ -193,9 +273,18 @@ const loadCommunity = async () => { |
|
|
|
pageSize: 6, |
|
|
|
pageNo: 1 |
|
|
|
}); |
|
|
|
communityList.value = data; |
|
|
|
// Only keep image and video data from API, use i18n for text |
|
|
|
communityList.value = data.map((item, index) => ({ |
|
|
|
id: item.id || index + 1, |
|
|
|
title: t(`community.highlights.default.item${index + 1}.title`), |
|
|
|
description: t(`community.highlights.default.item${index + 1}.description`), |
|
|
|
content: t(`community.highlights.default.item${index + 1}.content`), |
|
|
|
createTime: item.createTime, |
|
|
|
image: item.image, |
|
|
|
video: item.video |
|
|
|
})); |
|
|
|
} catch (error) { |
|
|
|
console.error('加载社区数据失败:', error); |
|
|
|
console.error(t('common.error.loadCommunityFailed'), error); |
|
|
|
} finally { |
|
|
|
communityLoading.value = false; |
|
|
|
} |
|
|
|
@ -219,7 +308,7 @@ const submitComment = async (forumId: string) => { |
|
|
|
newMessage.value.content = ''; |
|
|
|
newMessage.value.forumId = null; |
|
|
|
} catch (error) { |
|
|
|
console.error('提交评论失败:', error); |
|
|
|
console.error(t('common.error.submitCommentFailed'), error); |
|
|
|
} finally { |
|
|
|
isSubmitting.value = false; |
|
|
|
} |
|
|
|
@ -271,16 +360,21 @@ const getTimeAgo = (timestamp: string) => { |
|
|
|
const now = new Date(); |
|
|
|
const postTime = new Date(timestamp); |
|
|
|
const diffTime = Math.abs(now.getTime() - postTime.getTime()); |
|
|
|
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); |
|
|
|
const diffSeconds = Math.floor(diffTime / 1000); |
|
|
|
const diffMinutes = Math.floor(diffSeconds / 60); |
|
|
|
const diffHours = Math.floor(diffMinutes / 60); |
|
|
|
const diffDays = Math.floor(diffHours / 24); |
|
|
|
|
|
|
|
if (diffDays < 1) { |
|
|
|
return t('community.time.today'); |
|
|
|
} else if (diffDays === 1) { |
|
|
|
return t('community.time.yesterday'); |
|
|
|
if (diffSeconds < 60) { |
|
|
|
return t('common.time.just_now'); |
|
|
|
} else if (diffMinutes < 60) { |
|
|
|
return t('common.time.minutes_ago', { count: diffMinutes }); |
|
|
|
} else if (diffHours < 24) { |
|
|
|
return t('common.time.hours_ago', { count: diffHours }); |
|
|
|
} else if (diffDays < 7) { |
|
|
|
return t('community.time.days_ago', { days: diffDays }); |
|
|
|
return t('common.time.days_ago', { count: diffDays }); |
|
|
|
} else { |
|
|
|
return new Date(timestamp).toLocaleDateString(); |
|
|
|
return new Date(timestamp).toLocaleDateString(locale.value === 'zh' ? 'zh-CN' : 'en-US'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
@ -341,95 +435,114 @@ onMounted(async() => { |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
|
|
|
|
<!-- 官方公告 Section --> |
|
|
|
<!-- Community Introduction Section (formerly Official Announcements) --> |
|
|
|
<section class="py-16 px-6 md:px-12 lg:px-24" :style="{ backgroundImage: `url(${getConfigImage('com_mes_bg')})` }"> |
|
|
|
<div class="container mx-auto"> |
|
|
|
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast"> |
|
|
|
官方公告 |
|
|
|
{{ t('community.introduction.title') }} |
|
|
|
</h2> |
|
|
|
|
|
|
|
<div v-if="messageLoading" class="flex justify-center py-10"> |
|
|
|
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-primary"></div> |
|
|
|
<div class="bg-background/80 backdrop-blur-sm rounded-xl p-8 shadow-card max-w-4xl mx-auto"> |
|
|
|
<div class="prose prose-lg max-w-none text-white leading-relaxed wow animate__animated animate__fadeIn"> |
|
|
|
<p class="first-letter:text-4xl first-letter:font-bold first-letter:text-primary first-letter:mr-1 first-letter:float-left" v-html="t('community.introduction.content')"></p> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- <div class="mt-8 flex justify-center"> |
|
|
|
<a href="#community-activities" class="inline-flex items-center px-6 py-3 bg-primary text-white rounded-full hover:bg-primary-dark transition-colors duration-300 transform hover:scale-105"> |
|
|
|
<span>了解更多社区活动</span> |
|
|
|
<Icon icon="carbon:arrow-down" class="ml-2" /> |
|
|
|
</a> |
|
|
|
</div> --> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-else class="space-y-6"> |
|
|
|
<div |
|
|
|
v-for="message in messageList" |
|
|
|
:key="message.id" |
|
|
|
class="flex bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp" |
|
|
|
> |
|
|
|
<!-- 公告图片 --> |
|
|
|
<div class="w-1/4"> |
|
|
|
<img :src="message.image || '/LOGO.png'" :alt="message.title" class="w-full h-full object-cover" /> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 公告内容 --> |
|
|
|
<div class="w-3/4 p-6"> |
|
|
|
<h3 |
|
|
|
class="text-xl font-bold text-text mb-3 line-clamp-2 hover:text-primary hover:bg-primary/10 px-2 py-1 rounded transition-all duration-300 cursor-pointer hover:underline transform hover:scale-105" |
|
|
|
@click="showMessageDetail(message)" |
|
|
|
> |
|
|
|
{{ message.title }} |
|
|
|
</h3> |
|
|
|
<p class="text-text-secondary text-base mb-4 line-clamp-3">{{ message.description }}</p> |
|
|
|
<div class="flex items-center text-text-secondary text-sm"> |
|
|
|
<Icon icon="carbon:time" class="mr-2" /> |
|
|
|
<span>{{ formatDate(message.createTime) }}</span> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
|
|
|
|
<!-- Original official announcement content (commented out) --> |
|
|
|
<!-- |
|
|
|
<section class="py-16 px-6 md:px-12 lg:px-24" :style="{ backgroundImage: `url(${getConfigImage('com_mes_bg')})` }"> |
|
|
|
<div class="container mx-auto"> |
|
|
|
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast"> |
|
|
|
官方公告 |
|
|
|
</h2> |
|
|
|
|
|
|
|
<div v-if="messageLoading" class="flex justify-center py-10"> |
|
|
|
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-primary"></div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-else class="space-y-6"> |
|
|
|
<div |
|
|
|
v-for="message in messageList" |
|
|
|
:key="message.id" |
|
|
|
class="flex bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp" |
|
|
|
> |
|
|
|
<div class="w-1/4"> |
|
|
|
<img :src="message.image || '/LOGO.png'" :alt="message.title" class="w-full h-full object-cover" /> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="w-3/4 p-6"> |
|
|
|
<h3 |
|
|
|
class="text-xl font-bold text-text mb-3 line-clamp-2 hover:text-primary hover:bg-primary/10 px-2 py-1 rounded transition-all duration-300 cursor-pointer hover:underline transform hover:scale-105" |
|
|
|
@click="showMessageDetail(message)" |
|
|
|
> |
|
|
|
{{ message.title }} |
|
|
|
</h3> |
|
|
|
<p class="text-text-secondary text-base mb-4 line-clamp-3">{{ message.description }}</p> |
|
|
|
<div class="flex items-center text-text-secondary text-sm"> |
|
|
|
<Icon icon="carbon:time" class="mr-2" /> |
|
|
|
<span>{{ formatDate(message.createTime) }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 消息详情弹窗 --> |
|
|
|
<div |
|
|
|
v-if="showDetailModal" |
|
|
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4" |
|
|
|
@click="closeMessageDetail" |
|
|
|
> |
|
|
|
|
|
|
|
<div |
|
|
|
class="bg-background rounded-xl max-w-2xl w-full max-h-[80vh] overflow-y-auto" |
|
|
|
@click.stop |
|
|
|
v-if="showDetailModal" |
|
|
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4" |
|
|
|
@click="closeMessageDetail" |
|
|
|
> |
|
|
|
<!-- 弹窗头部 --> |
|
|
|
<div class="p-6 border-b border-border"> |
|
|
|
<div class="flex justify-between items-start"> |
|
|
|
<h3 class="text-2xl font-bold text-text">{{ selectedMessage?.title }}</h3> |
|
|
|
<button |
|
|
|
@click="closeMessageDetail" |
|
|
|
class="text-text-secondary hover:text-text transition-colors duration-300" |
|
|
|
> |
|
|
|
<Icon icon="carbon:close" class="h-6 w-6" /> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
<div class="flex items-center text-text-secondary text-sm mt-2"> |
|
|
|
<Icon icon="carbon:time" class="mr-2" /> |
|
|
|
<span>{{ formatDate(selectedMessage?.createTime) }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 弹窗内容 --> |
|
|
|
<div class="p-6"> |
|
|
|
<div v-if="selectedMessage?.image" class="mb-6"> |
|
|
|
<img |
|
|
|
:src="selectedMessage.image" |
|
|
|
:alt="selectedMessage.title" |
|
|
|
class="w-full h-64 object-cover rounded-lg" |
|
|
|
/> |
|
|
|
<div |
|
|
|
class="bg-background rounded-xl max-w-2xl w-full max-h-[80vh] overflow-y-auto" |
|
|
|
@click.stop |
|
|
|
> |
|
|
|
<div class="p-6 border-b border-border"> |
|
|
|
<div class="flex justify-between items-start"> |
|
|
|
<h3 class="text-2xl font-bold text-text">{{ selectedMessage?.title }}</h3> |
|
|
|
<button |
|
|
|
@click="closeMessageDetail" |
|
|
|
class="text-text-secondary hover:text-text transition-colors duration-300" |
|
|
|
> |
|
|
|
<Icon icon="carbon:close" class="h-6 w-6" /> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
<div class="flex items-center text-text-secondary text-sm mt-2"> |
|
|
|
<Icon icon="carbon:time" class="mr-2" /> |
|
|
|
<span>{{ formatDate(selectedMessage?.createTime) }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="prose prose-lg max-w-none"> |
|
|
|
<div v-html="selectedMessage?.description"></div> |
|
|
|
|
|
|
|
<div class="p-6"> |
|
|
|
<div v-if="selectedMessage?.image" class="mb-6"> |
|
|
|
<img |
|
|
|
:src="selectedMessage.image" |
|
|
|
:alt="selectedMessage.title" |
|
|
|
class="w-full h-64 object-cover rounded-lg" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<div class="prose prose-lg max-w-none"> |
|
|
|
<div v-html="selectedMessage?.content"></div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
</section> |
|
|
|
--> |
|
|
|
|
|
|
|
<!-- 社区风采 Section (原社区亮点) --> |
|
|
|
<!-- Community Highlights Section (formerly Community Highlights) --> |
|
|
|
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light" :style="{ backgroundImage: `url(${getConfigImage('com_show_bg')})` }"> |
|
|
|
<div class="container mx-auto"> |
|
|
|
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast"> |
|
|
|
社区风采 |
|
|
|
{{ t('community.highlights.title') }} |
|
|
|
</h2> |
|
|
|
|
|
|
|
<div v-if="communityLoading" class="flex justify-center py-10"> |
|
|
|
@ -451,7 +564,7 @@ onMounted(async() => { |
|
|
|
<SwiperSlide v-for="community in communityList" :key="community.id"> |
|
|
|
<div class="bg-background rounded-2xl overflow-hidden shadow-xl hover:shadow-2xl transition-all duration-500 h-full transform hover:scale-105"> |
|
|
|
<div class="flex flex-col h-full"> |
|
|
|
<div class="relative overflow-hidden"> |
|
|
|
<div class="relative overflow-hidden cursor-pointer" @click="showVideo(community)"> |
|
|
|
<img |
|
|
|
:src="community.image || '/LOGO.png'" |
|
|
|
:alt="community.title" |
|
|
|
@ -460,7 +573,12 @@ onMounted(async() => { |
|
|
|
<div class="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent"></div> |
|
|
|
</div> |
|
|
|
<div class="p-8 flex-1 flex flex-col"> |
|
|
|
<h3 class="text-2xl font-bold text-text mb-4">{{ community.title }}</h3> |
|
|
|
<h3 |
|
|
|
class="text-2xl font-bold text-text mb-4 cursor-pointer hover:text-primary transition-colors duration-300" |
|
|
|
@click="showCommunityDetail(community)" |
|
|
|
> |
|
|
|
{{ community.title }} |
|
|
|
</h3> |
|
|
|
<p class="text-text-secondary mb-6 flex-1 leading-relaxed">{{ community.description }}</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -468,7 +586,7 @@ onMounted(async() => { |
|
|
|
</SwiperSlide> |
|
|
|
</Swiper> |
|
|
|
|
|
|
|
<!-- 外部自定义导航按钮 --> |
|
|
|
<!-- External custom navigation buttons --> |
|
|
|
<button |
|
|
|
@click="swiperInstance?.slidePrev()" |
|
|
|
class="absolute left-4 top-1/2 transform -translate-y-1/2 w-12 h-12 bg-white/90 hover:bg-white rounded-full shadow-lg flex items-center justify-center transition-all duration-300 z-10 group" |
|
|
|
@ -484,7 +602,7 @@ onMounted(async() => { |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 外部自定义分页器 --> |
|
|
|
<!-- External custom pagination --> |
|
|
|
<div class="flex justify-center mt-8 gap-4"> |
|
|
|
<button |
|
|
|
v-for="(community, index) in communityList" |
|
|
|
@ -500,7 +618,7 @@ onMounted(async() => { |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
|
|
|
|
<!-- 社交媒体账号 Section --> |
|
|
|
<!-- Social Media Accounts Section --> |
|
|
|
<section class="py-16 px-6 md:px-12 lg:px-24" :style="{ backgroundImage: `url(${getConfigImage('com_media_bg')})` }"> |
|
|
|
<div class="container mx-auto"> |
|
|
|
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast"> |
|
|
|
@ -523,7 +641,7 @@ onMounted(async() => { |
|
|
|
> |
|
|
|
<div class="flex items-center mb-4"> |
|
|
|
<div class="w-12 h-12 rounded-full bg-primary bg-opacity-10 flex items-center justify-center mr-4"> |
|
|
|
<Icon :icon="getSocialIcon(account.title)" class="h-6 w-6 text-primary" /> |
|
|
|
<img :src="account.image" alt="" class="w-12 h-12 rounded-full"> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<h3 class="text-lg font-bold text-text">{{ account.title }}</h3> |
|
|
|
@ -533,7 +651,7 @@ onMounted(async() => { |
|
|
|
|
|
|
|
<p class="text-text-secondary mb-4" v-html="account.description"></p> |
|
|
|
|
|
|
|
<a |
|
|
|
<!-- <a |
|
|
|
:href="account.url" |
|
|
|
target="_blank" |
|
|
|
rel="noopener noreferrer" |
|
|
|
@ -543,13 +661,13 @@ onMounted(async() => { |
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> |
|
|
|
</svg> |
|
|
|
</a> |
|
|
|
</a> --> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
|
|
|
|
<!-- 社区论坛 Section --> |
|
|
|
<!-- Community Forum Section --> |
|
|
|
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light" :style="{ backgroundImage: `url(${getConfigImage('com_forum_bg')})` }" > |
|
|
|
<div class="container mx-auto"> |
|
|
|
<h2 class="text-2xl md:text-3xl font-bold text-text mb-8 text-center wow animate__animated animate__fadeInUp animate__duration-fast"> |
|
|
|
@ -570,20 +688,19 @@ onMounted(async() => { |
|
|
|
:key="forum.id" |
|
|
|
class="bg-background rounded-xl shadow-card overflow-hidden wow animate__animated animate__fadeInUp" |
|
|
|
> |
|
|
|
<!-- 帖子内容 --> |
|
|
|
<!-- Post content --> |
|
|
|
<div class="p-6"> |
|
|
|
<div class="flex items-center mb-4"> |
|
|
|
<div class="w-10 h-10 rounded-full bg-primary bg-opacity-10 flex items-center justify-center mr-3"> |
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" /> |
|
|
|
</svg> |
|
|
|
<!-- TailwindCSS properties for image scaling --> |
|
|
|
<img :src="forum.image" class="w-10 h-10 rounded-full object-cover"> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<h3 class="text-lg font-bold text-text">{{ forum.title }}</h3> |
|
|
|
<div class="flex items-center text-text-secondary text-sm"> |
|
|
|
<span>{{ forum.createBy }}</span> |
|
|
|
<span class="mx-2">•</span> |
|
|
|
<span>{{ formatDate(forum.createTime) }}</span> |
|
|
|
<span>{{ formatDate(forum.createTime || '') }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -618,7 +735,7 @@ onMounted(async() => { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 评论区 --> |
|
|
|
<!-- Comments section --> |
|
|
|
<div v-if="currentForumId === forum.id"> |
|
|
|
<div class="border-t border-background-light"> |
|
|
|
<div class="p-6"> |
|
|
|
@ -661,7 +778,7 @@ onMounted(async() => { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 评论表单 --> |
|
|
|
<!-- Comment form --> |
|
|
|
<div class="bg-background-dark rounded-lg p-4"> |
|
|
|
<textarea |
|
|
|
v-model="newMessage.content" |
|
|
|
@ -681,7 +798,7 @@ onMounted(async() => { |
|
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> |
|
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
|
|
|
</svg> |
|
|
|
{{ t('community.forum.submitting') }} |
|
|
|
{{ t('community.forum.submit') }} |
|
|
|
</span> |
|
|
|
<span v-else>{{ t('community.forum.submit') }}</span> |
|
|
|
</button> |
|
|
|
@ -695,7 +812,7 @@ onMounted(async() => { |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
|
|
|
|
<!-- 3. 信息公示 Section --> |
|
|
|
<!-- 3. Information Disclosure Section --> |
|
|
|
<!-- <section class="py-16 px-6 md:px-12 lg:px-24" :style="{ backgroundImage: `url(${getConfigImage('com_mes_bg')})` }"> |
|
|
|
<div class="container mx-auto"> |
|
|
|
<h2 class="text-3xl font-bold text-text mb-4 text-center wow animate__animated animate__fadeInUp"> |
|
|
|
@ -708,7 +825,7 @@ onMounted(async() => { |
|
|
|
|
|
|
|
<div v-else-if="messageList.length === 0" class="text-center py-10"> |
|
|
|
<Icon icon="carbon:no-content" class="mx-auto mb-4" width="48" height="48" /> |
|
|
|
<p class="text-text-secondary">暂无信息公示数据</p> |
|
|
|
<p class="text-text-secondary">{{ t('community.announcements.no_data') }}</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto mt-8"> |
|
|
|
@ -736,7 +853,7 @@ onMounted(async() => { |
|
|
|
</div> |
|
|
|
</section> --> |
|
|
|
|
|
|
|
<section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light" :style="{ backgroundImage: `url(${getConfigImage('com_show_bg')})` }"> |
|
|
|
<!-- <section class="py-16 px-6 md:px-12 lg:px-24 bg-background-light" :style="{ backgroundImage: `url(${getConfigImage('com_show_bg')})` }"> |
|
|
|
<div class="container mx-auto"> |
|
|
|
<h2 class="text-3xl font-bold text-text mb-4 text-center wow animate__animated animate__fadeInUp"> |
|
|
|
{{ t('community.highlights.title') }} |
|
|
|
@ -748,7 +865,7 @@ onMounted(async() => { |
|
|
|
|
|
|
|
<div v-else-if="communityList.length === 0" class="text-center py-10"> |
|
|
|
<Icon icon="carbon:no-content" class="mx-auto mb-4" width="48" height="48" /> |
|
|
|
<p class="text-text-secondary">暂无社区活动数据</p> |
|
|
|
<p class="text-text-secondary">{{ t('community.highlights.no_data') }}</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-else class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8"> |
|
|
|
@ -771,7 +888,66 @@ onMounted(async() => { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
</section> --> |
|
|
|
|
|
|
|
<!-- Video playback modal --> |
|
|
|
<div v-if="showVideoModal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50" @click="closeVideoModal"> |
|
|
|
<div class="relative max-w-4xl w-full mx-4" @click.stop> |
|
|
|
<button |
|
|
|
@click="closeVideoModal" |
|
|
|
class="absolute -top-12 right-0 text-white hover:text-gray-300 transition-colors" |
|
|
|
> |
|
|
|
<Icon icon="carbon:close" class="h-8 w-8" /> |
|
|
|
</button> |
|
|
|
<div class="bg-black rounded-lg overflow-hidden"> |
|
|
|
<video |
|
|
|
v-if="selectedCommunity?.video" |
|
|
|
:src="selectedCommunity.video" |
|
|
|
controls |
|
|
|
autoplay |
|
|
|
class="w-full h-auto max-h-[70vh]" |
|
|
|
> |
|
|
|
{{ t('community.video.unsupported') }} |
|
|
|
</video> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- Community details modal --> |
|
|
|
<div v-if="showCommunityDetailModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" @click="closeCommunityDetailModal"> |
|
|
|
<div class="bg-background rounded-xl max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto" @click.stop> |
|
|
|
<div class="p-6"> |
|
|
|
<div class="flex justify-between items-start mb-4"> |
|
|
|
<h3 class="text-2xl font-bold text-text">{{ selectedCommunity?.title }}</h3> |
|
|
|
<button |
|
|
|
@click="closeCommunityDetailModal" |
|
|
|
class="text-text-secondary hover:text-text transition-colors" |
|
|
|
> |
|
|
|
<Icon icon="carbon:close" class="h-6 w-6" /> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="selectedCommunity?.image" class="mb-6"> |
|
|
|
<img |
|
|
|
:src="selectedCommunity.image" |
|
|
|
:alt="selectedCommunity.title" |
|
|
|
class="w-full h-64 object-cover rounded-lg" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="prose prose-lg max-w-none text-text-secondary"> |
|
|
|
<div v-html="selectedCommunity?.content"></div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="selectedCommunity?.createTime" class="mt-6 pt-4 border-t border-background-light"> |
|
|
|
<div class="flex items-center text-text-secondary text-sm"> |
|
|
|
<Icon icon="carbon:time" class="mr-2" /> |
|
|
|
<span>{{ formatDate(selectedCommunity.createTime) }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
@ -792,7 +968,7 @@ onMounted(async() => { |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
|
|
|
|
/* Swiper分页器样式 */ |
|
|
|
/* Swiper pagination styles */ |
|
|
|
:deep(.swiper-pagination-bullet) { |
|
|
|
width: 12px; |
|
|
|
height: 12px; |
|
|
|
@ -814,4 +990,62 @@ onMounted(async() => { |
|
|
|
bottom: 0; |
|
|
|
margin-top: 20px; |
|
|
|
} |
|
|
|
</style> |
|
|
|
|
|
|
|
/* Modal styles */ |
|
|
|
.fixed { |
|
|
|
position: fixed; |
|
|
|
} |
|
|
|
|
|
|
|
.inset-0 { |
|
|
|
top: 0; |
|
|
|
right: 0; |
|
|
|
bottom: 0; |
|
|
|
left: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.z-50 { |
|
|
|
z-index: 50; |
|
|
|
} |
|
|
|
|
|
|
|
/* Video modal animations */ |
|
|
|
.video-modal-enter-active, |
|
|
|
.video-modal-leave-active { |
|
|
|
transition: opacity 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.video-modal-enter-from, |
|
|
|
.video-modal-leave-to { |
|
|
|
opacity: 0; |
|
|
|
} |
|
|
|
|
|
|
|
/* Detail modal animations */ |
|
|
|
.detail-modal-enter-active, |
|
|
|
.detail-modal-leave-active { |
|
|
|
transition: all 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.detail-modal-enter-from, |
|
|
|
.detail-modal-leave-to { |
|
|
|
opacity: 0; |
|
|
|
transform: scale(0.9); |
|
|
|
} |
|
|
|
|
|
|
|
/* Scrollbar styles */ |
|
|
|
.overflow-y-auto::-webkit-scrollbar { |
|
|
|
width: 6px; |
|
|
|
} |
|
|
|
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-track { |
|
|
|
background: rgba(0, 0, 0, 0.1); |
|
|
|
border-radius: 3px; |
|
|
|
} |
|
|
|
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-thumb { |
|
|
|
background: rgba(0, 0, 0, 0.3); |
|
|
|
border-radius: 3px; |
|
|
|
} |
|
|
|
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-thumb:hover { |
|
|
|
background: rgba(0, 0, 0, 0.5); |
|
|
|
} |
|
|
|
</style> |