1669 lines
40 KiB
Vue
1669 lines
40 KiB
Vue
|
<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>
|