<script setup lang="ts">
|
|
import { useI18n } from 'vue-i18n';
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import { Icon } from '@iconify/vue';
|
|
import { useConfig } from '@/utils/config';
|
|
import { Swiper, SwiperSlide } from 'swiper/vue';
|
|
import { Navigation, Pagination } from 'swiper/modules';
|
|
import 'swiper/css';
|
|
import 'swiper/css/navigation';
|
|
import 'swiper/css/pagination';
|
|
import {
|
|
queryOfficialMediaList,
|
|
queryForumList,
|
|
queryCommentsList,
|
|
queryMessageList,
|
|
queryCommunityList,
|
|
addComments,
|
|
type OfficialMediaItem,
|
|
type ForumItem,
|
|
type CommentItem,
|
|
type MessageItem,
|
|
type CommunityItem
|
|
} from '@/api/modules';
|
|
|
|
const { getConfigImage } = useConfig();
|
|
|
|
const { t } = useI18n();
|
|
|
|
// 社交媒体账号数据
|
|
const socialMediaAccounts = ref<OfficialMediaItem[]>([]);
|
|
const socialMediaLoading = ref(true);
|
|
|
|
// 论坛信息数据
|
|
const forumList = ref<ForumItem[]>([]);
|
|
const forumLoading = ref(true);
|
|
|
|
// 评论信息数据
|
|
const commentsMap = ref<Record<string, CommentItem[]>>({});
|
|
const commentsLoading = ref(true);
|
|
|
|
// 信息公示数据
|
|
const messageList = ref<MessageItem[]>([]);
|
|
const messageLoading = ref(true);
|
|
|
|
// 社区活动数据
|
|
const communityList = ref<CommunityItem[]>([]);
|
|
const communityLoading = ref(true);
|
|
|
|
// 新留言
|
|
const newMessage = ref({
|
|
forumId: null as string | null,
|
|
content: '',
|
|
createBy: ''
|
|
});
|
|
|
|
// 是否正在提交
|
|
const isSubmitting = ref(false);
|
|
|
|
// 当前查看的帖子ID
|
|
const currentForumId = ref<string | null>(null);
|
|
|
|
// Swiper实例
|
|
const swiperInstance = ref(null);
|
|
|
|
// Swiper配置
|
|
const swiperOptions = {
|
|
slidesPerView: 3,
|
|
spaceBetween: 40,
|
|
navigation: false,
|
|
pagination: false,
|
|
modules: [Navigation, Pagination],
|
|
breakpoints: {
|
|
320: {
|
|
slidesPerView: 1,
|
|
spaceBetween: 20
|
|
},
|
|
768: {
|
|
slidesPerView: 2,
|
|
spaceBetween: 30
|
|
},
|
|
1024: {
|
|
slidesPerView: 3,
|
|
spaceBetween: 40
|
|
}
|
|
}
|
|
};
|
|
|
|
// 弹窗相关数据
|
|
const showDetailModal = ref(false);
|
|
const selectedMessage = ref<MessageItem | null>(null);
|
|
|
|
// 显示消息详情弹窗
|
|
const showMessageDetail = (message: MessageItem) => {
|
|
selectedMessage.value = message;
|
|
showDetailModal.value = true;
|
|
};
|
|
|
|
// 关闭消息详情弹窗
|
|
const closeMessageDetail = () => {
|
|
showDetailModal.value = false;
|
|
selectedMessage.value = null;
|
|
};
|
|
|
|
// 跳转到公告详情
|
|
const goToMessage = (id: string) => {
|
|
console.log('查看公告详情:', id);
|
|
// 跳转到公告详情页面实现
|
|
};
|
|
|
|
// 格式化日期
|
|
const formatDate = (dateString: string) => {
|
|
if (!dateString) return '';
|
|
|
|
const date = new Date(dateString);
|
|
return new Intl.DateTimeFormat('zh-CN', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
}).format(date);
|
|
};
|
|
|
|
// 加载社交媒体数据
|
|
const loadSocialMedia = async () => {
|
|
try {
|
|
socialMediaLoading.value = true;
|
|
const data = await queryOfficialMediaList({
|
|
pageSize: 10,
|
|
pageNo: 1
|
|
});
|
|
socialMediaAccounts.value = data;
|
|
} catch (error) {
|
|
console.error('加载社交媒体数据失败:', error);
|
|
} finally {
|
|
socialMediaLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// 加载论坛数据
|
|
const loadForum = async () => {
|
|
try {
|
|
forumLoading.value = true;
|
|
const data = await queryForumList({
|
|
pageSize: 10,
|
|
pageNo: 1
|
|
});
|
|
forumList.value = data;
|
|
|
|
// 为每个论坛加载对应的评论
|
|
await Promise.all(data.map(forum => loadComments(forum.id)));
|
|
} catch (error) {
|
|
console.error('加载论坛数据失败:', error);
|
|
} finally {
|
|
forumLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// 加载评论数据
|
|
const loadComments = async (forumId: string) => {
|
|
try {
|
|
commentsLoading.value = true;
|
|
const data = await queryCommentsList({
|
|
forumId
|
|
});
|
|
commentsMap.value[forumId] = data;
|
|
} catch (error) {
|
|
console.error(`加载论坛${forumId}的评论数据失败:`, error);
|
|
} finally {
|
|
commentsLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// 加载信息公示数据
|
|
const loadMessages = async () => {
|
|
try {
|
|
messageLoading.value = true;
|
|
const data = await queryMessageList({
|
|
pageSize: 10,
|
|
pageNo: 1
|
|
});
|
|
messageList.value = data;
|
|
} catch (error) {
|
|
console.error('加载信息公示数据失败:', error);
|
|
} finally {
|
|
messageLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// 加载社区数据
|
|
const loadCommunity = async () => {
|
|
try {
|
|
communityLoading.value = true;
|
|
const data = await queryCommunityList({
|
|
pageSize: 6,
|
|
pageNo: 1
|
|
});
|
|
communityList.value = data;
|
|
} catch (error) {
|
|
console.error('加载社区数据失败:', error);
|
|
} finally {
|
|
communityLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// 提交留言
|
|
const submitComment = async (forumId: string) => {
|
|
if (newMessage.value.content) {
|
|
isSubmitting.value = true;
|
|
try {
|
|
await addComments({
|
|
forumId: forumId,
|
|
content: newMessage.value.content
|
|
});
|
|
|
|
// 重新加载评论
|
|
await loadComments(forumId);
|
|
|
|
// 重置表单
|
|
newMessage.value.createBy = '';
|
|
newMessage.value.content = '';
|
|
newMessage.value.forumId = null;
|
|
} catch (error) {
|
|
console.error('提交评论失败:', error);
|
|
} finally {
|
|
isSubmitting.value = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
// 准备留言
|
|
const prepareComment = (forumId: string) => {
|
|
// 记录当前滚动位置
|
|
const scrollPosition = window.scrollY;
|
|
|
|
// 设置当前查看的帖子ID
|
|
currentForumId.value = forumId;
|
|
newMessage.value.forumId = forumId;
|
|
|
|
// 使用setTimeout确保DOM更新后再恢复滚动位置
|
|
setTimeout(() => {
|
|
window.scrollTo({
|
|
top: scrollPosition,
|
|
behavior: 'auto'
|
|
});
|
|
}, 0);
|
|
};
|
|
|
|
// 关闭评论
|
|
const closeComment = () => {
|
|
// 记录当前滚动位置
|
|
const scrollPosition = window.scrollY;
|
|
|
|
// 关闭评论区
|
|
currentForumId.value = null;
|
|
|
|
// 使用setTimeout确保DOM更新后再恢复滚动位置
|
|
setTimeout(() => {
|
|
window.scrollTo({
|
|
top: scrollPosition,
|
|
behavior: 'auto'
|
|
});
|
|
}, 0);
|
|
};
|
|
|
|
// 获取特定论坛的评论
|
|
const getComments = (forumId: string): CommentItem[] => {
|
|
return commentsMap.value[forumId] || [];
|
|
};
|
|
|
|
// 计算帖子发布时间
|
|
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));
|
|
|
|
if (diffDays < 1) {
|
|
return t('community.time.today');
|
|
} else if (diffDays === 1) {
|
|
return t('community.time.yesterday');
|
|
} else if (diffDays < 7) {
|
|
return t('community.time.days_ago', { days: diffDays });
|
|
} else {
|
|
return new Date(timestamp).toLocaleDateString();
|
|
}
|
|
};
|
|
|
|
// 根据平台名称获取图标
|
|
const getSocialIcon = (title: string) => {
|
|
const lowerTitle = title.toLowerCase();
|
|
if (lowerTitle.includes('twitter') || lowerTitle.includes('x')) {
|
|
return 'mdi:twitter';
|
|
} else if (lowerTitle.includes('telegram')) {
|
|
return 'mdi:telegram';
|
|
} else if (lowerTitle.includes('discord')) {
|
|
return 'mdi:discord';
|
|
} else if (lowerTitle.includes('medium')) {
|
|
return 'mdi:medium';
|
|
} else if (lowerTitle.includes('github')) {
|
|
return 'mdi:github';
|
|
} else if (lowerTitle.includes('reddit')) {
|
|
return 'mdi:reddit';
|
|
} else if (lowerTitle.includes('facebook')) {
|
|
return 'mdi:facebook';
|
|
} else if (lowerTitle.includes('instagram')) {
|
|
return 'mdi:instagram';
|
|
} else {
|
|
return 'mdi:web';
|
|
}
|
|
};
|
|
|
|
onMounted(async() => {
|
|
await Promise.all([
|
|
loadSocialMedia(),
|
|
loadForum(),
|
|
loadMessages(),
|
|
loadCommunity()
|
|
]);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bg-background min-h-screen">
|
|
<!-- Hero Section -->
|
|
<section class="relative py-24 px-6 md:px-12 lg:px-24 bg-background-dark overflow-hidden">
|
|
<div class="container mx-auto relative z-10">
|
|
<div class="max-w-3xl mx-auto text-center">
|
|
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-text mb-6 wow animate__animated animate__fadeInDown animate__duration-fast">
|
|
{{ t('community.hero.title') }}
|
|
</h1>
|
|
<p class="text-lg md:text-xl text-text-secondary mb-8 wow animate__animated animate__fadeIn animate__delay-xs animate__duration-fast">
|
|
{{ t('community.hero.subtitle') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Background Decoration -->
|
|
<div class="absolute top-0 left-0 w-full h-full overflow-hidden opacity-10">
|
|
<div class="absolute -top-24 -left-24 w-64 h-64 rounded-full bg-primary-light blur-3xl wow animate__animated animate__pulse animate__infinite"></div>
|
|
<div class="absolute top-1/2 right-0 w-80 h-80 rounded-full bg-secondary blur-3xl wow animate__animated animate__pulse animate__infinite animate__delay-sm"></div>
|
|
<div class="absolute -bottom-24 left-1/3 w-72 h-72 rounded-full bg-accent blur-3xl wow animate__animated animate__pulse animate__infinite animate__delay-md"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 官方公告 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-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
|
|
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
|
|
>
|
|
<!-- 弹窗头部 -->
|
|
<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>
|
|
<div class="prose prose-lg max-w-none">
|
|
<div v-html="selectedMessage?.description"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 社区风采 Section (原社区亮点) -->
|
|
<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">
|
|
社区风采
|
|
</h2>
|
|
|
|
<div v-if="communityLoading" 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="relative">
|
|
<div class="relative">
|
|
<Swiper
|
|
:slides-per-view="swiperOptions.slidesPerView"
|
|
:space-between="swiperOptions.spaceBetween"
|
|
:navigation="swiperOptions.navigation"
|
|
:pagination="false"
|
|
:modules="swiperOptions.modules"
|
|
:breakpoints="swiperOptions.breakpoints"
|
|
class="community-swiper"
|
|
@swiper="swiperInstance = $event"
|
|
>
|
|
<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">
|
|
<img
|
|
:src="community.image || '/LOGO.png'"
|
|
:alt="community.title"
|
|
class="w-full h-56 object-cover transition-transform duration-700 hover:scale-110"
|
|
/>
|
|
<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>
|
|
<p class="text-text-secondary mb-6 flex-1 leading-relaxed">{{ community.description }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</SwiperSlide>
|
|
</Swiper>
|
|
|
|
<!-- 外部自定义导航按钮 -->
|
|
<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"
|
|
>
|
|
<Icon icon="carbon:chevron-left" class="h-6 w-6 text-primary group-hover:scale-110 transition-transform duration-300" />
|
|
</button>
|
|
|
|
<button
|
|
@click="swiperInstance?.slideNext()"
|
|
class="absolute right-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"
|
|
>
|
|
<Icon icon="carbon:chevron-right" class="h-6 w-6 text-primary group-hover:scale-110 transition-transform duration-300" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 外部自定义分页器 -->
|
|
<div class="flex justify-center mt-8 gap-4">
|
|
<button
|
|
v-for="(community, index) in communityList"
|
|
:key="index"
|
|
@click="swiperInstance?.slideTo(index)"
|
|
class="w-5 h-5 rounded-full transition-all duration-300 border-2 cursor-pointer"
|
|
:class="swiperInstance?.activeIndex === index
|
|
? 'bg-primary border-primary scale-125 shadow-lg'
|
|
: 'bg-white/50 border-white/70 hover:bg-white/80'"
|
|
></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 社交媒体账号 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">
|
|
{{ t('community.social_media.title') }}
|
|
</h2>
|
|
|
|
<div v-if="socialMediaLoading" 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-if="socialMediaAccounts.length === 0" class="text-center py-10 text-text-secondary">
|
|
{{ t('community.social_media.no_accounts') }}
|
|
</div>
|
|
|
|
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
<div
|
|
v-for="account in socialMediaAccounts"
|
|
:key="account.id"
|
|
class="bg-background rounded-xl p-6 shadow-card hover:shadow-lg transition-all duration-300 wow animate__animated animate__fadeInUp"
|
|
>
|
|
<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" />
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-bold text-text">{{ account.title }}</h3>
|
|
<p class="text-text-secondary text-sm">{{ account.username }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-text-secondary mb-4" v-html="account.description"></p>
|
|
|
|
<a
|
|
:href="account.url"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="inline-flex items-center text-primary-light hover:text-primary-dark transition-colors"
|
|
>
|
|
<span>{{ t('community.social_media.follow_us') }}</span>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 社区论坛 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">
|
|
{{ t('community.forum.title') }}
|
|
</h2>
|
|
|
|
<div v-if="forumLoading" 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-if="forumList.length === 0" class="text-center py-10 text-text-secondary">
|
|
{{ t('community.forum.no_topics') }}
|
|
</div>
|
|
|
|
<div v-else class="space-y-6">
|
|
<div
|
|
v-for="forum in forumList"
|
|
:key="forum.id"
|
|
class="bg-background rounded-xl shadow-card overflow-hidden wow animate__animated animate__fadeInUp"
|
|
>
|
|
<!-- 帖子内容 -->
|
|
<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>
|
|
</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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<p class="text-text-secondary" v-html="forum.content"></p>
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center space-x-4">
|
|
<button class="flex items-center text-text-secondary hover:text-primary transition-colors">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5" />
|
|
</svg>
|
|
<span>{{ forum.likeCount || 0 }}</span>
|
|
</button>
|
|
|
|
<button class="flex items-center text-text-secondary hover:text-primary transition-colors" @click="prepareComment(forum.id)">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" 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>
|
|
<span>{{ getComments(forum.id).length }}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<button class="flex items-center text-primary-light hover:text-primary-dark transition-colors" @click="prepareComment(forum.id)">
|
|
<span>{{ t('community.forum.add_comment') }}</span>
|
|
<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="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 评论区 -->
|
|
<div v-if="currentForumId === forum.id">
|
|
<div class="border-t border-background-light">
|
|
<div class="p-6">
|
|
<h4 class="text-lg font-bold text-text mb-4 flex items-center justify-between">
|
|
<span>{{ t('community.forum.view_comments') }}</span>
|
|
<button class="text-text-secondary hover:text-primary transition-colors" @click="closeComment">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</h4>
|
|
|
|
<div v-if="commentsLoading" class="flex justify-center py-4">
|
|
<div class="animate-spin rounded-full h-6 w-6 border-t-2 border-b-2 border-primary"></div>
|
|
</div>
|
|
|
|
<div v-else-if="getComments(forum.id).length === 0" class="text-center py-4 text-text-secondary">
|
|
{{ t('community.forum.no_comments') }}
|
|
</div>
|
|
|
|
<div v-else class="space-y-4 mb-6">
|
|
<div
|
|
v-for="comment in getComments(forum.id)"
|
|
:key="comment.id"
|
|
class="bg-background-dark rounded-lg p-4"
|
|
>
|
|
<div class="flex items-center mb-2">
|
|
<div class="w-8 h-8 rounded-full bg-primary bg-opacity-10 flex items-center justify-center mr-2">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h5 class="text-sm font-medium text-text">{{ comment.createBy || t('community.forum.username') }}</h5>
|
|
<p class="text-xs text-text-secondary">{{ formatDate(comment.createTime) }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-text-secondary" v-html="comment.content"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 评论表单 -->
|
|
<div class="bg-background-dark rounded-lg p-4">
|
|
<textarea
|
|
v-model="newMessage.content"
|
|
:placeholder="t('community.forum.comment')"
|
|
class="w-full bg-background border border-background-light rounded-lg p-3 text-text-secondary resize-none focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
|
rows="3"
|
|
></textarea>
|
|
|
|
<div class="flex justify-end mt-3">
|
|
<button
|
|
@click="submitComment(forum.id)"
|
|
:disabled="!newMessage.content || isSubmitting"
|
|
class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<span v-if="isSubmitting">
|
|
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<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') }}
|
|
</span>
|
|
<span v-else>{{ t('community.forum.submit') }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 3. 信息公示 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">
|
|
{{ t('community.announcements.title') }}
|
|
</h2>
|
|
|
|
<div v-if="messageLoading" class="flex justify-center items-center py-12">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
|
|
</div>
|
|
|
|
<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>
|
|
</div>
|
|
|
|
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto mt-8">
|
|
<div
|
|
v-for="message in messageList"
|
|
:key="message.id"
|
|
class="bg-background-light rounded-xl p-6 shadow-card hover:shadow-lg transition-all duration-300"
|
|
>
|
|
<div class="flex items-start gap-4">
|
|
<div v-if="message.image" class="w-16 h-16 rounded-lg overflow-hidden flex-shrink-0">
|
|
<img :src="message.image" :alt="message.title" class="w-full h-full object-cover" />
|
|
</div>
|
|
<div v-else class="w-16 h-16 rounded-lg bg-primary bg-opacity-10 flex items-center justify-center flex-shrink-0">
|
|
<Icon icon="carbon:notification" width="32" height="32" class="text-primary" />
|
|
</div>
|
|
|
|
<div class="flex-1">
|
|
<h3 class="text-lg font-bold text-text mb-2">{{ message.title }}</h3>
|
|
<p class="text-sm text-text-secondary mb-2" v-html="message.content"></p>
|
|
<div class="text-xs text-text-secondary">{{ formatDate(message.createTime || '') }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section> -->
|
|
|
|
<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') }}
|
|
</h2>
|
|
|
|
<div v-if="communityLoading" class="flex justify-center items-center py-12">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
|
|
</div>
|
|
|
|
<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>
|
|
</div>
|
|
|
|
<div v-else class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
|
|
<div
|
|
v-for="community in communityList"
|
|
:key="community.id"
|
|
class="bg-background rounded-xl overflow-hidden shadow-card hover:shadow-lg transition-all duration-300"
|
|
>
|
|
<div v-if="community.image" class="h-48 overflow-hidden">
|
|
<img :src="community.image" :alt="community.title" class="w-full h-full object-cover hover:scale-105 transition-transform duration-300">
|
|
</div>
|
|
<div v-else class="h-48 bg-primary bg-opacity-10 flex items-center justify-center">
|
|
<Icon icon="carbon:events" width="64" height="64" class="text-primary opacity-50" />
|
|
</div>
|
|
<div class="p-6">
|
|
<h4 class="text-lg font-bold text-text mb-2">{{ community.title }}</h4>
|
|
<p class="text-text-secondary text-sm mb-2" v-html="community.content"></p>
|
|
<p class="text-text-secondary text-xs">{{ formatDate(community.createTime || '') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.shadow-card {
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
}
|
|
|
|
.shadow-card:hover {
|
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
.line-clamp-2 {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Swiper分页器样式 */
|
|
:deep(.swiper-pagination-bullet) {
|
|
width: 12px;
|
|
height: 12px;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
opacity: 1;
|
|
border: 2px solid rgba(255, 255, 255, 0.8);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
:deep(.swiper-pagination-bullet-active) {
|
|
background: var(--color-primary);
|
|
border-color: var(--color-primary);
|
|
transform: scale(1.3);
|
|
box-shadow: 0 0 10px rgba(var(--color-primary-rgb), 0.5);
|
|
}
|
|
|
|
:deep(.swiper-pagination) {
|
|
position: relative;
|
|
bottom: 0;
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|