Skip to main content
Glama
Home.vue10.3 kB
<!-- views/Home.vue - 首页视图组件 --> <template> <div class="home"> <el-row :gutter="20"> <!-- 左侧边栏 --> <el-col :span="6"> <div class="sidebar"> <!-- 分类列表 --> <el-card class="category-card"> <template #header> <div class="card-header"> <span>分类</span> </div> </template> <div class="category-list"> <el-menu :default-active="activeCategory" @select="handleCategorySelect" > <el-menu-item index="all"> <span>全部</span> </el-menu-item> <el-menu-item v-for="category in categories" :key="category.id" :index="category.id.toString()" > <span>{{ category.name }}</span> </el-menu-item> </el-menu> </div> </el-card> </div> </el-col> <!-- 右侧内容区 --> <el-col :span="18"> <div class="content-area"> <!-- 顶部操作栏 --> <div class="action-bar"> <el-button type="primary" @click="handleCreatePost"> <el-icon><Plus /></el-icon> 发布新帖 </el-button> <el-input v-model="searchKeyword" placeholder="搜索帖子" class="search-input" clearable @keyup.enter="handleSearch" > <template #suffix> <el-icon class="el-input__icon" @click="handleSearch"> <Search /> </el-icon> </template> </el-input> </div> <!-- 帖子列表 --> <div class="post-list"> <!-- 加载状态 --> <div v-if="loading" class="loading-container"> <el-skeleton :rows="10" animated /> </div> <!-- 帖子列表内容 --> <template v-else> <el-empty v-if="posts.length === 0" description="暂无帖子" /> <el-card v-for="post in posts" :key="post.id" class="post-item" shadow="hover"> <div class="post-item-content" @click="viewPostDetail(post.id)"> <div class="post-item-header"> <h3 class="post-item-title">{{ post.title }}</h3> <el-tag size="small" type="info">{{ getCategoryName(post.categoryId) }}</el-tag> </div> <div class="post-item-body"> <p class="post-item-summary">{{ truncateContent(post.content) }}</p> </div> <div class="post-item-footer"> <div class="post-item-author"> <el-avatar :size="24" :src="post.author.avatar || ''"> {{ post.author.username.charAt(0).toUpperCase() }} </el-avatar> <span class="author-name">{{ post.author.username }}</span> </div> <div class="post-item-meta"> <span class="post-item-time">{{ formatDate(post.createTime) }}</span> <span class="post-item-stats"> <el-icon><View /></el-icon> {{ post.viewCount || 0 }} <el-icon><ChatDotRound /></el-icon> {{ post.commentCount || 0 }} <el-icon><Star /></el-icon> {{ post.likeCount || 0 }} </span> </div> </div> </div> </el-card> <!-- 分页 --> <div class="pagination-container"> <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[10, 20, 30, 50]" layout="total, sizes, prev, pager, next, jumper" :total="totalPosts" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </template> </div> </div> </el-col> </el-row> </div> </template> <script> import { ref, computed, onMounted, watch } from 'vue' import { useStore } from 'vuex' import { useRouter } from 'vue-router' import { Plus, Search, View, ChatDotRound, Star } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' export default { name: 'Home', components: { Plus, Search, View, ChatDotRound, Star }, setup() { const store = useStore() const router = useRouter() // 状态 const loading = ref(false) const searchKeyword = ref('') const currentPage = ref(1) const pageSize = ref(10) const totalPosts = ref(0) // 从store获取数据 const posts = computed(() => store.getters.allPosts) const categories = computed(() => store.getters.getCategories) const activeCategory = computed(() => store.getters.getActiveCategory) // 获取帖子列表 const fetchPosts = async () => { loading.value = true try { const response = await store.dispatch('fetchPosts', { categoryId: activeCategory.value, page: currentPage.value - 1, // 后端分页从0开始 size: pageSize.value }) totalPosts.value = response.totalElements || 0 } catch (error) { ElMessage.error('获取帖子列表失败') console.error('获取帖子列表失败:', error) } finally { loading.value = false } } // 监听分类变化,重新获取帖子 watch(activeCategory, () => { currentPage.value = 1 // 切换分类时重置页码 fetchPosts() }) // 处理分类选择 const handleCategorySelect = (categoryId) => { store.dispatch('setActiveCategory', categoryId) } // 处理页码变化 const handleCurrentChange = (page) => { currentPage.value = page fetchPosts() } // 处理每页条数变化 const handleSizeChange = (size) => { pageSize.value = size fetchPosts() } // 处理搜索 const handleSearch = () => { if (searchKeyword.value.trim()) { // 这里应该调用搜索API ElMessage.info(`搜索: ${searchKeyword.value}`) // 实际项目中需要实现后端搜索API } } // 处理创建帖子 const handleCreatePost = () => { // 检查用户是否已登录 if (!store.getters.isAuthenticated) { ElMessage.warning('请先登录') router.push('/login') return } router.push('/create-post') } // 查看帖子详情 const viewPostDetail = (postId) => { router.push(`/post/${postId}`) } // 获取分类名称 const getCategoryName = (categoryId) => { const category = categories.value.find(c => c.id === categoryId) return category ? category.name : '未分类' } // 截断内容 const truncateContent = (content) => { if (!content) return '' return content.length > 100 ? content.substring(0, 100) + '...' : content } // 格式化日期 const formatDate = (dateString) => { if (!dateString) return '' const date = new Date(dateString) const now = new Date() const diff = now - date // 一分钟内 if (diff < 60 * 1000) { return '刚刚' } // 一小时内 if (diff < 60 * 60 * 1000) { return `${Math.floor(diff / (60 * 1000))}分钟前` } // 一天内 if (diff < 24 * 60 * 60 * 1000) { return `${Math.floor(diff / (60 * 60 * 1000))}小时前` } // 一周内 if (diff < 7 * 24 * 60 * 60 * 1000) { return `${Math.floor(diff / (24 * 60 * 60 * 1000))}天前` } // 其他 return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` } // 组件挂载时获取帖子列表 onMounted(() => { fetchPosts() }) return { loading, posts, categories, activeCategory, searchKeyword, currentPage, pageSize, totalPosts, handleCategorySelect, handleCurrentChange, handleSizeChange, handleSearch, handleCreatePost, viewPostDetail, getCategoryName, truncateContent, formatDate } } } </script> <style scoped> .home { width: 100%; } .sidebar { position: sticky; top: 80px; } .category-card { margin-bottom: 20px; } .card-header { display: flex; justify-content: space-between; align-items: center; } .action-bar { display: flex; justify-content: space-between; margin-bottom: 20px; } .search-input { width: 300px; } .post-item { margin-bottom: 15px; cursor: pointer; transition: all 0.3s; } .post-item:hover { transform: translateY(-3px); } .post-item-content { display: flex; flex-direction: column; } .post-item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .post-item-title { margin: 0; font-size: 18px; color: #303133; } .post-item-summary { color: #606266; margin: 0 0 10px 0; line-height: 1.5; } .post-item-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 10px; font-size: 14px; color: #909399; } .post-item-author { display: flex; align-items: center; } .author-name { margin-left: 8px; } .post-item-meta { display: flex; align-items: center; } .post-item-time { margin-right: 15px; } .post-item-stats { display: flex; align-items: center; } .post-item-stats .el-icon { margin: 0 5px 0 15px; } .pagination-container { margin-top: 30px; display: flex; justify-content: center; } .loading-container { padding: 20px; } </style>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TonyCui35/Forum_Web'

If you have feedback or need assistance with the MCP directory API, please join our Discord server