iGuwan-homework/src/views/social/ForumView.vue

1669 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="forum-view">
<!-- 背景动画 -->
<div class="background-animation">
<div class="floating-shapes">
<div v-for="i in 15" :key="i" class="shape" :style="{
left: Math.random() * 100 + '%',
top: Math.random() * 100 + '%',
animationDelay: Math.random() * 5 + 's'
}"></div>
</div>
</div>
<!-- 主要内容 -->
<div class="main-content">
<!-- 页面标题 -->
<div class="page-header">
<h1 class="page-title">学习论坛</h1>
<p class="page-subtitle">交流学习心得,分享知识经验</p>
</div>
<!-- 论坛导航 -->
<div class="forum-nav">
<button
v-for="tab in forumTabs"
:key="tab.id"
:class="['nav-tab', { active: activeTab === tab.id }]"
@click="activeTab = tab.id"
>
<i class="tab-icon">{{ tab.icon }}</i>
{{ tab.name }}
</button>
<button @click="showNewPostModal = true" class="new-post-btn">
<i class="icon">✏️</i>
发布新帖
</button>
</div>
<!-- 搜索和筛选 -->
<div class="search-filter-section">
<div class="search-box">
<input
v-model="searchQuery"
type="text"
placeholder="搜索帖子..."
class="search-input"
>
<button class="search-btn">
<i class="search-icon">🔍</i>
</button>
</div>
<div class="filter-options">
<select v-model="selectedCategory" class="filter-select">
<option value="all">全部分类</option>
<option value="study">学习讨论</option>
<option value="homework">作业求助</option>
<option value="exam">考试交流</option>
<option value="resource">资源分享</option>
<option value="life">校园生活</option>
</select>
<select v-model="selectedSubject" class="filter-select">
<option value="all">全部科目</option>
<option value="数学">数学</option>
<option value="语文">语文</option>
<option value="英语">英语</option>
<option value="物理">物理</option>
<option value="化学">化学</option>
</select>
<select v-model="sortBy" class="filter-select">
<option value="latest">最新发布</option>
<option value="hot">热门讨论</option>
<option value="replies">回复最多</option>
<option value="likes">点赞最多</option>
</select>
</div>
</div>
<!-- 论坛统计 -->
<div class="stats-section">
<div class="stat-card">
<div class="stat-icon">📝</div>
<div class="stat-info">
<span class="stat-number">{{ totalPosts }}</span>
<span class="stat-label">总帖子数</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">💬</div>
<div class="stat-info">
<span class="stat-number">{{ totalReplies }}</span>
<span class="stat-label">总回复数</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">👥</div>
<div class="stat-info">
<span class="stat-number">{{ activeUsers }}</span>
<span class="stat-label">活跃用户</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">🔥</div>
<div class="stat-info">
<span class="stat-number">{{ todayPosts }}</span>
<span class="stat-label">今日新帖</span>
</div>
</div>
</div>
<!-- 热门话题 -->
<div v-if="activeTab === 'hot'" class="hot-topics-section">
<h2 class="section-title">🔥 热门话题</h2>
<div class="topics-grid">
<div v-for="topic in hotTopics" :key="topic.id" class="topic-card">
<div class="topic-header">
<div class="topic-info">
<h3 class="topic-title">{{ topic.title }}</h3>
<div class="topic-meta">
<span class="topic-category">{{ topic.category }}</span>
<span class="topic-subject">{{ topic.subject }}</span>
</div>
</div>
<div class="topic-stats">
<div class="stat-item">
<i class="icon">👁️</i>
<span>{{ topic.views }}</span>
</div>
<div class="stat-item">
<i class="icon">💬</i>
<span>{{ topic.replies }}</span>
</div>
</div>
</div>
<p class="topic-description">{{ topic.description }}</p>
<div class="topic-footer">
<div class="author-info">
<img :src="topic.author.avatar" :alt="topic.author.name" class="author-avatar">
<div class="author-details">
<span class="author-name">{{ topic.author.name }}</span>
<span class="post-time">{{ formatTime(topic.createdAt) }}</span>
</div>
</div>
<button @click="viewTopic(topic)" class="view-topic-btn">
查看详情
</button>
</div>
</div>
</div>
</div>
<!-- 最新帖子 -->
<div v-if="activeTab === 'latest'" class="posts-section">
<h2 class="section-title">📝 最新帖子</h2>
<div class="posts-list">
<div v-for="post in filteredPosts" :key="post.id" class="post-card">
<div class="post-header">
<div class="post-info">
<h3 class="post-title" @click="viewPost(post)">{{ post.title }}</h3>
<div class="post-meta">
<span :class="['category-tag', post.category]">{{ getCategoryText(post.category) }}</span>
<span class="subject-tag">{{ post.subject }}</span>
<span v-if="post.isSticky" class="sticky-tag">📌 置顶</span>
<span v-if="post.isHot" class="hot-tag">🔥 热门</span>
</div>
</div>
<div class="post-actions">
<button @click="likePost(post)" :class="['like-btn', { liked: post.isLiked }]">
<i class="icon">{{ post.isLiked ? '❤️' : '🤍' }}</i>
<span>{{ post.likes }}</span>
</button>
<button @click="favoritePost(post)" :class="['favorite-btn', { favorited: post.isFavorited }]">
<i class="icon">{{ post.isFavorited ? '⭐' : '☆' }}</i>
</button>
</div>
</div>
<p class="post-content">{{ post.content }}</p>
<div v-if="post.images && post.images.length > 0" class="post-images">
<img v-for="(image, index) in post.images" :key="index" :src="image" :alt="`图片${index + 1}`" class="post-image">
</div>
<div class="post-footer">
<div class="author-info">
<img :src="post.author.avatar" :alt="post.author.name" class="author-avatar">
<div class="author-details">
<span class="author-name">{{ post.author.name }}</span>
<span class="author-level">{{ post.author.level }}</span>
<span class="post-time">{{ formatTime(post.createdAt) }}</span>
</div>
</div>
<div class="post-stats">
<div class="stat-item">
<i class="icon">👁️</i>
<span>{{ post.views }}浏览</span>
</div>
<div class="stat-item">
<i class="icon">💬</i>
<span>{{ post.replies }}回复</span>
</div>
<div class="stat-item">
<i class="icon">👍</i>
<span>{{ post.likes }}点赞</span>
</div>
</div>
</div>
<div v-if="post.lastReply" class="last-reply">
<div class="reply-info">
<span class="reply-label">最新回复:</span>
<span class="reply-author">{{ post.lastReply.author }}</span>
<span class="reply-time">{{ formatTime(post.lastReply.time) }}</span>
</div>
<p class="reply-content">{{ post.lastReply.content }}</p>
</div>
</div>
</div>
</div>
<!-- 我的帖子 -->
<div v-if="activeTab === 'my'" class="my-posts-section">
<h2 class="section-title">📋 我的帖子</h2>
<div class="my-posts-stats">
<div class="stat-card">
<div class="stat-icon">📝</div>
<div class="stat-info">
<span class="stat-number">{{ myPosts.length }}</span>
<span class="stat-label">发布帖子</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">💬</div>
<div class="stat-info">
<span class="stat-number">{{ myRepliesCount }}</span>
<span class="stat-label">回复数量</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">👍</div>
<div class="stat-info">
<span class="stat-number">{{ myLikesCount }}</span>
<span class="stat-label">获得点赞</span>
</div>
</div>
</div>
<div class="posts-list">
<div v-for="post in myPosts" :key="post.id" class="post-card my-post">
<div class="post-header">
<div class="post-info">
<h3 class="post-title" @click="viewPost(post)">{{ post.title }}</h3>
<div class="post-meta">
<span :class="['category-tag', post.category]">{{ getCategoryText(post.category) }}</span>
<span class="subject-tag">{{ post.subject }}</span>
<span :class="['status-tag', post.status]">{{ getStatusText(post.status) }}</span>
</div>
</div>
<div class="post-actions">
<button @click="editPost(post)" class="edit-btn">
<i class="icon">✏️</i>
编辑
</button>
<button @click="deletePost(post)" class="delete-btn">
<i class="icon">🗑️</i>
删除
</button>
</div>
</div>
<p class="post-content">{{ post.content }}</p>
<div class="post-footer">
<div class="post-time">
发布时间: {{ formatTime(post.createdAt) }}
</div>
<div class="post-stats">
<div class="stat-item">
<i class="icon">👁️</i>
<span>{{ post.views }}</span>
</div>
<div class="stat-item">
<i class="icon">💬</i>
<span>{{ post.replies }}</span>
</div>
<div class="stat-item">
<i class="icon">👍</i>
<span>{{ post.likes }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 收藏帖子 -->
<div v-if="activeTab === 'favorites'" class="favorites-section">
<h2 class="section-title">⭐ 我的收藏</h2>
<div class="posts-list">
<div v-for="post in favoritePosts" :key="post.id" class="post-card favorite-post">
<div class="post-header">
<div class="post-info">
<h3 class="post-title" @click="viewPost(post)">{{ post.title }}</h3>
<div class="post-meta">
<span :class="['category-tag', post.category]">{{ getCategoryText(post.category) }}</span>
<span class="subject-tag">{{ post.subject }}</span>
</div>
</div>
<div class="post-actions">
<button @click="favoritePost(post)" class="unfavorite-btn">
<i class="icon">⭐</i>
取消收藏
</button>
</div>
</div>
<p class="post-content">{{ post.content }}</p>
<div class="post-footer">
<div class="author-info">
<img :src="post.author.avatar" :alt="post.author.name" class="author-avatar">
<div class="author-details">
<span class="author-name">{{ post.author.name }}</span>
<span class="post-time">{{ formatTime(post.createdAt) }}</span>
</div>
</div>
<div class="favorite-time">
收藏时间: {{ formatTime(post.favoritedAt) }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 发布新帖弹窗 -->
<div v-if="showNewPostModal" class="modal-overlay" @click="closeNewPostModal">
<div class="new-post-modal" @click.stop>
<div class="modal-header">
<h3>发布新帖</h3>
<button @click="closeNewPostModal" class="close-btn">×</button>
</div>
<div class="modal-content">
<form @submit.prevent="submitNewPost">
<div class="form-group">
<label>帖子标题</label>
<input v-model="newPost.title" type="text" placeholder="请输入帖子标题" required>
</div>
<div class="form-row">
<div class="form-group">
<label>分类</label>
<select v-model="newPost.category" required>
<option value="">选择分类</option>
<option value="study">学习讨论</option>
<option value="homework">作业求助</option>
<option value="exam">考试交流</option>
<option value="resource">资源分享</option>
<option value="life">校园生活</option>
</select>
</div>
<div class="form-group">
<label>科目</label>
<select v-model="newPost.subject">
<option value="">选择科目</option>
<option value="数学">数学</option>
<option value="语文">语文</option>
<option value="英语">英语</option>
<option value="物理">物理</option>
<option value="化学">化学</option>
<option value="其他">其他</option>
</select>
</div>
</div>
<div class="form-group">
<label>帖子内容</label>
<textarea v-model="newPost.content" placeholder="请输入帖子内容" rows="8" required></textarea>
</div>
<div class="form-group">
<label>添加图片 (可选)</label>
<input type="file" multiple accept="image/*" @change="handleImageUpload">
<div v-if="newPost.images.length > 0" class="image-preview">
<div v-for="(image, index) in newPost.images" :key="index" class="preview-item">
<img :src="image" :alt="`预览${index + 1}`" class="preview-image">
<button type="button" @click="removeImage(index)" class="remove-image">×</button>
</div>
</div>
</div>
<div class="form-actions">
<button type="button" @click="closeNewPostModal" class="cancel-btn">取消</button>
<button type="submit" class="submit-btn">发布帖子</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// 响应式数据
const activeTab = ref('latest')
const searchQuery = ref('')
const selectedCategory = ref('all')
const selectedSubject = ref('all')
const sortBy = ref('latest')
const showNewPostModal = ref(false)
// 论坛导航标签
const forumTabs = [
{ id: 'hot', name: '热门话题', icon: '🔥' },
{ id: 'latest', name: '最新帖子', icon: '📝' },
{ id: 'my', name: '我的帖子', icon: '📋' },
{ id: 'favorites', name: '我的收藏', icon: '⭐' }
]
// 新帖子数据
const newPost = ref({
title: '',
category: '',
subject: '',
content: '',
images: []
})
// 热门话题数据
const hotTopics = ref([
{
id: 1,
title: '高考数学复习策略分享',
category: 'study',
subject: '数学',
description: '分享一些高考数学复习的有效方法和技巧,希望对大家有帮助。',
views: 2345,
replies: 156,
author: {
name: '学霸小王',
avatar: 'https://via.placeholder.com/40x40?text=王'
},
createdAt: new Date('2024-01-25')
},
{
id: 2,
title: '英语作文写作技巧讨论',
category: 'study',
subject: '英语',
description: '大家来讨论一下英语作文的写作技巧,如何提高作文分数。',
views: 1876,
replies: 89,
author: {
name: '英语达人',
avatar: 'https://via.placeholder.com/40x40?text=英'
},
createdAt: new Date('2024-01-24')
},
{
id: 3,
title: '物理实验报告模板分享',
category: 'resource',
subject: '物理',
description: '整理了一些物理实验报告的标准模板,需要的同学可以参考。',
views: 1234,
replies: 67,
author: {
name: '实验小能手',
avatar: 'https://via.placeholder.com/40x40?text=实'
},
createdAt: new Date('2024-01-23')
}
])
// 帖子数据
const posts = ref([
{
id: 1,
title: '求助:这道数学题怎么解?',
category: 'homework',
subject: '数学',
content: '这是一道关于函数的题目我不太理解解题思路希望有同学能帮忙解答一下。题目是求函数f(x)=x²+2x-3的最值...',
images: ['https://via.placeholder.com/300x200?text=数学题目'],
views: 234,
replies: 12,
likes: 8,
isLiked: false,
isFavorited: false,
isSticky: false,
isHot: false,
author: {
name: '小明同学',
avatar: 'https://via.placeholder.com/40x40?text=明',
level: '初学者'
},
createdAt: new Date('2024-01-28'),
lastReply: {
author: '数学老师',
content: '这道题可以通过配方法来解决...',
time: new Date('2024-01-28')
}
},
{
id: 2,
title: '分享一些英语学习资源',
category: 'resource',
subject: '英语',
content: '最近整理了一些很好的英语学习资源,包括听力材料、阅读文章和语法练习,分享给大家。',
images: [],
views: 567,
replies: 23,
likes: 45,
isLiked: true,
isFavorited: true,
isSticky: true,
isHot: true,
author: {
name: '英语小助手',
avatar: 'https://via.placeholder.com/40x40?text=助',
level: '资深用户'
},
createdAt: new Date('2024-01-27'),
lastReply: {
author: '学习爱好者',
content: '谢谢分享,这些资源很有用!',
time: new Date('2024-01-28')
}
},
{
id: 3,
title: '期末考试复习计划讨论',
category: 'exam',
subject: '全科',
content: '期末考试快到了,大家都是怎么制定复习计划的?来交流一下经验吧。',
images: [],
views: 789,
replies: 34,
likes: 56,
isLiked: false,
isFavorited: false,
isSticky: false,
isHot: true,
author: {
name: '复习达人',
avatar: 'https://via.placeholder.com/40x40?text=复',
level: '高级用户'
},
createdAt: new Date('2024-01-26'),
lastReply: {
author: '时间管理大师',
content: '我建议按照艾宾浩斯遗忘曲线来安排复习...',
time: new Date('2024-01-27')
}
}
])
// 我的帖子数据
const myPosts = ref([
{
id: 101,
title: '我的学习心得分享',
category: 'study',
subject: '数学',
content: '经过一学期的学习,我总结了一些数学学习的心得体会...',
views: 123,
replies: 8,
likes: 15,
status: 'published',
createdAt: new Date('2024-01-20')
},
{
id: 102,
title: '关于物理实验的疑问',
category: 'homework',
subject: '物理',
content: '在做物理实验时遇到了一些问题,希望能得到解答...',
views: 67,
replies: 3,
likes: 5,
status: 'published',
createdAt: new Date('2024-01-18')
}
])
// 收藏帖子数据
const favoritePosts = ref([
{
id: 2,
title: '分享一些英语学习资源',
category: 'resource',
subject: '英语',
content: '最近整理了一些很好的英语学习资源...',
author: {
name: '英语小助手',
avatar: 'https://via.placeholder.com/40x40?text=助'
},
createdAt: new Date('2024-01-27'),
favoritedAt: new Date('2024-01-28')
}
])
// 计算属性
const totalPosts = computed(() => posts.value.length + 1247) // 模拟总数
const totalReplies = computed(() => posts.value.reduce((sum, post) => sum + post.replies, 0) + 5678)
const activeUsers = computed(() => 234)
const todayPosts = computed(() => 12)
const myRepliesCount = computed(() => 45)
const myLikesCount = computed(() => myPosts.value.reduce((sum, post) => sum + post.likes, 0))
const filteredPosts = computed(() => {
let filtered = posts.value
// 搜索过滤
if (searchQuery.value) {
filtered = filtered.filter(post =>
post.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
post.content.toLowerCase().includes(searchQuery.value.toLowerCase())
)
}
// 分类过滤
if (selectedCategory.value !== 'all') {
filtered = filtered.filter(post => post.category === selectedCategory.value)
}
// 科目过滤
if (selectedSubject.value !== 'all') {
filtered = filtered.filter(post => post.subject === selectedSubject.value)
}
// 排序
switch (sortBy.value) {
case 'hot':
filtered.sort((a, b) => (b.views + b.replies * 2 + b.likes * 3) - (a.views + a.replies * 2 + a.likes * 3))
break
case 'replies':
filtered.sort((a, b) => b.replies - a.replies)
break
case 'likes':
filtered.sort((a, b) => b.likes - a.likes)
break
default: // latest
filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
}
return filtered
})
// 方法
const getCategoryText = (category) => {
const categoryMap = {
'study': '学习讨论',
'homework': '作业求助',
'exam': '考试交流',
'resource': '资源分享',
'life': '校园生活'
}
return categoryMap[category] || '其他'
}
const getStatusText = (status) => {
const statusMap = {
'published': '已发布',
'draft': '草稿',
'hidden': '已隐藏'
}
return statusMap[status] || '未知'
}
const formatTime = (date) => {
const now = new Date()
const diff = now - date
const minutes = Math.floor(diff / 60000)
const hours = Math.floor(diff / 3600000)
const days = Math.floor(diff / 86400000)
if (minutes < 1) return '刚刚'
if (minutes < 60) return `${minutes}分钟前`
if (hours < 24) return `${hours}小时前`
if (days < 7) return `${days}天前`
return date.toLocaleDateString('zh-CN')
}
const viewTopic = (topic) => {
console.log('查看话题:', topic.title)
// router.push(`/forum/topic/${topic.id}`)
}
const viewPost = (post) => {
console.log('查看帖子:', post.title)
// router.push(`/forum/post/${post.id}`)
}
const likePost = (post) => {
post.isLiked = !post.isLiked
post.likes += post.isLiked ? 1 : -1
console.log('点赞状态:', post.isLiked ? '已点赞' : '取消点赞')
}
const favoritePost = (post) => {
post.isFavorited = !post.isFavorited
console.log('收藏状态:', post.isFavorited ? '已收藏' : '取消收藏')
}
const editPost = (post) => {
console.log('编辑帖子:', post.title)
// 实现编辑功能
}
const deletePost = (post) => {
if (confirm('确定要删除这个帖子吗?')) {
const index = myPosts.value.findIndex(p => p.id === post.id)
if (index > -1) {
myPosts.value.splice(index, 1)
}
console.log('删除帖子:', post.title)
}
}
const closeNewPostModal = () => {
showNewPostModal.value = false
// 重置表单
newPost.value = {
title: '',
category: '',
subject: '',
content: '',
images: []
}
}
const handleImageUpload = (event) => {
const files = Array.from(event.target.files)
files.forEach(file => {
const reader = new FileReader()
reader.onload = (e) => {
newPost.value.images.push(e.target.result)
}
reader.readAsDataURL(file)
})
}
const removeImage = (index) => {
newPost.value.images.splice(index, 1)
}
const submitNewPost = () => {
// 验证表单
if (!newPost.value.title || !newPost.value.category || !newPost.value.content) {
alert('请填写完整信息')
return
}
// 创建新帖子
const post = {
id: Date.now(),
title: newPost.value.title,
category: newPost.value.category,
subject: newPost.value.subject || '其他',
content: newPost.value.content,
images: [...newPost.value.images],
views: 0,
replies: 0,
likes: 0,
isLiked: false,
isFavorited: false,
isSticky: false,
isHot: false,
author: {
name: '当前用户',
avatar: 'https://via.placeholder.com/40x40?text=我',
level: '普通用户'
},
createdAt: new Date()
}
// 添加到帖子列表
posts.value.unshift(post)
myPosts.value.unshift({
...post,
status: 'published'
})
console.log('发布新帖:', post.title)
closeNewPostModal()
}
onMounted(() => {
// 页面加载动画
const mainContent = document.querySelector('.main-content')
if (mainContent) {
mainContent.classList.add('loaded')
}
})
</script>
<style scoped>
.forum-view {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
overflow-x: hidden;
}
.background-animation {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.floating-shapes {
position: relative;
width: 100%;
height: 100%;
}
.shape {
position: absolute;
width: 8px;
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
animation: float 12s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-30px) rotate(180deg); }
}
.main-content {
position: relative;
z-index: 2;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease;
}
.main-content.loaded {
opacity: 1;
transform: translateY(0);
}
.page-header {
text-align: center;
margin-bottom: 40px;
color: white;
}
.page-title {
font-size: 3rem;
font-weight: bold;
margin-bottom: 10px;
background: linear-gradient(45deg, #fbbf24, #f59e0b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-subtitle {
font-size: 1.2rem;
opacity: 0.9;
margin: 0;
}
.forum-nav {
display: flex;
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 5px;
margin-bottom: 30px;
backdrop-filter: blur(10px);
align-items: center;
gap: 5px;
}
.nav-tab {
flex: 1;
padding: 15px 20px;
border: none;
background: transparent;
color: white;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.nav-tab:hover {
background: rgba(255, 255, 255, 0.1);
}
.nav-tab.active {
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.new-post-btn {
padding: 15px 20px;
background: linear-gradient(45deg, #10b981, #059669);
border: none;
border-radius: 10px;
color: white;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.new-post-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
}
.tab-icon {
font-size: 1.2rem;
}
.search-filter-section {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 25px;
margin-bottom: 30px;
backdrop-filter: blur(10px);
display: flex;
gap: 20px;
align-items: center;
flex-wrap: wrap;
}
.search-box {
flex: 1;
min-width: 300px;
display: flex;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
overflow: hidden;
}
.search-input {
flex: 1;
padding: 12px 15px;
border: none;
background: transparent;
color: white;
font-size: 1rem;
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.7);
}
.search-btn {
padding: 12px 15px;
border: none;
background: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
transition: background 0.3s ease;
}
.search-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.filter-options {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.filter-select {
padding: 10px 15px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
color: white;
backdrop-filter: blur(10px);
min-width: 120px;
}
.filter-select option {
background: #374151;
color: white;
}
.stats-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
display: flex;
align-items: center;
gap: 15px;
backdrop-filter: blur(10px);
transition: transform 0.3s ease;
color: white;
}
.stat-card:hover {
transform: translateY(-3px);
}
.stat-icon {
font-size: 2rem;
}
.stat-info {
display: flex;
flex-direction: column;
}
.stat-number {
font-size: 1.8rem;
font-weight: bold;
color: #fbbf24;
}
.stat-label {
font-size: 0.9rem;
opacity: 0.8;
}
.section-title {
font-size: 1.8rem;
font-weight: bold;
margin: 0 0 25px 0;
color: #fbbf24;
text-align: center;
}
.hot-topics-section,
.posts-section,
.my-posts-section,
.favorites-section {
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 30px;
backdrop-filter: blur(10px);
color: white;
}
.topics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 25px;
}
.topic-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 25px;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.topic-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
}
.topic-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px;
}
.topic-title {
font-size: 1.3rem;
font-weight: bold;
margin: 0 0 10px 0;
color: white;
}
.topic-meta {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.topic-category,
.topic-subject {
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
}
.topic-category {
background: rgba(251, 191, 36, 0.8);
}
.topic-subject {
background: rgba(59, 130, 246, 0.8);
}
.topic-stats {
display: flex;
flex-direction: column;
gap: 5px;
}
.stat-item {
display: flex;
align-items: center;
gap: 5px;
font-size: 0.9rem;
opacity: 0.8;
}
.topic-description {
margin: 0 0 20px 0;
line-height: 1.5;
opacity: 0.9;
}
.topic-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.author-info {
display: flex;
align-items: center;
gap: 10px;
}
.author-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.author-details {
display: flex;
flex-direction: column;
}
.author-name {
font-weight: bold;
font-size: 0.9rem;
}
.author-level {
font-size: 0.8rem;
opacity: 0.7;
color: #fbbf24;
}
.post-time {
font-size: 0.8rem;
opacity: 0.7;
}
.view-topic-btn {
padding: 8px 16px;
background: linear-gradient(45deg, #3b82f6, #2563eb);
border: none;
border-radius: 6px;
color: white;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
font-weight: bold;
}
.view-topic-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(59, 130, 246, 0.4);
}
.posts-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.post-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
padding: 25px;
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.post-card:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateY(-2px);
}
.post-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px;
}
.post-title {
font-size: 1.3rem;
font-weight: bold;
margin: 0 0 10px 0;
color: white;
cursor: pointer;
transition: color 0.3s ease;
}
.post-title:hover {
color: #fbbf24;
}
.post-meta {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.category-tag,
.subject-tag,
.sticky-tag,
.hot-tag,
.status-tag {
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
}
.category-tag {
background: rgba(251, 191, 36, 0.8);
}
.subject-tag {
background: rgba(59, 130, 246, 0.8);
}
.sticky-tag {
background: rgba(239, 68, 68, 0.8);
}
.hot-tag {
background: rgba(245, 101, 101, 0.8);
}
.status-tag {
background: rgba(16, 185, 129, 0.8);
}
.post-actions {
display: flex;
gap: 10px;
}
.like-btn,
.favorite-btn,
.edit-btn,
.delete-btn,
.unfavorite-btn {
padding: 8px 12px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 5px;
}
.like-btn {
background: rgba(239, 68, 68, 0.8);
color: white;
}
.like-btn.liked {
background: rgba(239, 68, 68, 1);
}
.favorite-btn {
background: rgba(251, 191, 36, 0.8);
color: white;
}
.favorite-btn.favorited {
background: rgba(251, 191, 36, 1);
}
.edit-btn {
background: rgba(59, 130, 246, 0.8);
color: white;
}
.delete-btn {
background: rgba(239, 68, 68, 0.8);
color: white;
}
.unfavorite-btn {
background: rgba(156, 163, 175, 0.8);
color: white;
}
.like-btn:hover,
.favorite-btn:hover,
.edit-btn:hover,
.delete-btn:hover,
.unfavorite-btn:hover {
transform: translateY(-1px);
opacity: 1;
}
.post-content {
margin: 0 0 15px 0;
line-height: 1.6;
opacity: 0.9;
}
.post-images {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.post-image {
max-width: 200px;
max-height: 150px;
border-radius: 8px;
object-fit: cover;
cursor: pointer;
transition: transform 0.3s ease;
}
.post-image:hover {
transform: scale(1.05);
}
.post-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.post-stats {
display: flex;
gap: 15px;
}
.last-reply {
margin-top: 15px;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
border-left: 3px solid #fbbf24;
}
.reply-info {
display: flex;
gap: 10px;
margin-bottom: 8px;
font-size: 0.9rem;
}
.reply-label {
font-weight: bold;
color: #fbbf24;
}
.reply-author {
font-weight: bold;
}
.reply-time {
opacity: 0.7;
}
.reply-content {
margin: 0;
font-size: 0.9rem;
opacity: 0.9;
line-height: 1.5;
}
.my-posts-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.my-post {
border-left: 3px solid #10b981;
}
.favorite-post {
border-left: 3px solid #fbbf24;
}
.post-time,
.favorite-time {
font-size: 0.9rem;
opacity: 0.8;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.new-post-modal {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 30px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.modal-header h3 {
margin: 0;
font-size: 1.5rem;
color: #fbbf24;
}
.close-btn {
background: none;
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.3s ease;
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #fbbf24;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
color: white;
backdrop-filter: blur(10px);
font-size: 1rem;
resize: vertical;
}
.form-group input::placeholder,
.form-group textarea::placeholder {
color: rgba(255, 255, 255, 0.7);
}
.form-group select option {
background: #374151;
color: white;
}
.image-preview {
display: flex;
gap: 10px;
margin-top: 10px;
flex-wrap: wrap;
}
.preview-item {
position: relative;
}
.preview-image {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 8px;
}
.remove-image {
position: absolute;
top: -5px;
right: -5px;
width: 20px;
height: 20px;
background: rgba(239, 68, 68, 0.9);
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 0.8rem;
display: flex;
align-items: center;
justify-content: center;
}
.form-actions {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 25px;
}
.cancel-btn,
.submit-btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.cancel-btn {
background: rgba(156, 163, 175, 0.8);
color: white;
}
.cancel-btn:hover {
background: rgba(156, 163, 175, 1);
}
.submit-btn {
background: linear-gradient(45deg, #10b981, #059669);
color: white;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
}
@media (max-width: 768px) {
.main-content {
padding: 15px;
}
.page-title {
font-size: 2.5rem;
}
.forum-nav {
flex-direction: column;
gap: 10px;
}
.nav-tab,
.new-post-btn {
width: 100%;
justify-content: center;
}
.search-filter-section {
flex-direction: column;
align-items: stretch;
}
.search-box {
min-width: auto;
}
.filter-options {
justify-content: center;
}
.stats-section {
grid-template-columns: repeat(2, 1fr);
}
.topics-grid {
grid-template-columns: 1fr;
}
.topic-header,
.post-header {
flex-direction: column;
gap: 15px;
}
.post-footer {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.post-stats {
align-self: stretch;
justify-content: space-around;
}
.my-posts-stats {
grid-template-columns: 1fr;
}
.form-row {
grid-template-columns: 1fr;
}
.new-post-modal {
margin: 20px;
padding: 20px;
}
.form-actions {
flex-direction: column;
}
}
</style>