blob: 703e4f592022ef1a6f876d2d648d904a569e5a36 [file] [log] [blame]
Xing Jinwenff16b1e2025-06-05 00:29:26 +08001<template>
2 <div class="forum-page">
Xing Jinwenebbccad2025-06-07 21:24:44 +08003 <Navbar />
Xing Jinwenff16b1e2025-06-05 00:29:26 +08004 <div class="page-container">
5 <!-- 论坛头部 -->
6 <div class="forum-header">
7 <div class="header-content">
8 <h1>社区论坛</h1>
9 <p class="header-description">与其他用户交流讨论,分享经验心得</p>
10 <div class="header-actions">
11 <el-button type="primary" :icon="Edit" @click="showNewTopicDialog = true">
12 发布新帖
13 </el-button>
14 </div>
15 </div>
16 </div>
17
18 <!-- 论坛统计 -->
19 <div class="forum-stats">
20 <div class="stats-grid">
21 <div class="stat-item">
22 <el-icon size="32" color="#409eff"><ChatDotRound /></el-icon>
23 <div class="stat-info">
24 <h3>{{ forumStats.totalTopics }}</h3>
25 <p>主题总数</p>
26 </div>
27 </div>
28 <div class="stat-item">
29 <el-icon size="32" color="#67c23a"><Comment /></el-icon>
30 <div class="stat-info">
31 <h3>{{ forumStats.totalReplies }}</h3>
32 <p>回复总数</p>
33 </div>
34 </div>
35 <div class="stat-item">
36 <el-icon size="32" color="#e6a23c"><User /></el-icon>
37 <div class="stat-info">
38 <h3>{{ forumStats.activeUsers }}</h3>
39 <p>活跃用户</p>
40 </div>
41 </div>
42 <div class="stat-item">
43 <el-icon size="32" color="#f56c6c"><View /></el-icon>
44 <div class="stat-info">
45 <h3>{{ forumStats.todayPosts }}</h3>
46 <p>今日发帖</p>
47 </div>
48 </div>
49 </div>
50 </div>
51
52 <!-- 版块列表 -->
53 <div class="forum-sections">
54 <h2 class="section-title">论坛版块</h2>
55 <div class="sections-list">
56 <div
57 v-for="section in forumSections"
58 :key="section.id"
59 class="section-card"
60 @click="navigateToSection(section.id)"
61 >
62 <div class="section-icon">
63 <el-icon size="48" :color="section.color">
64 <component :is="section.icon" />
65 </el-icon>
66 </div>
67 <div class="section-info">
68 <h3 class="section-name">{{ section.name }}</h3>
69 <p class="section-description">{{ section.description }}</p>
208159515458d95702025-06-09 14:46:58 +080070<!-- <div class="section-stats">-->
71<!-- <span class="stat">{{ section.topics }} 主题</span>-->
72<!-- <span class="stat">{{ section.replies }} 回复</span>-->
73<!-- </div>-->
Xing Jinwenff16b1e2025-06-05 00:29:26 +080074 </div>
75 <div class="section-latest">
76 <div v-if="section.latestTopic" class="latest-topic">
77 <p class="topic-title">{{ section.latestTopic.title }}</p>
78 <div class="topic-meta">
79 <span class="author">{{ section.latestTopic.author }}</span>
80 <span class="time">{{ formatTime(section.latestTopic.time) }}</span>
81 </div>
82 </div>
83 </div>
84 </div>
85 </div>
86 </div>
87
88 <!-- 热门主题 -->
89 <div class="hot-topics">
90 <div class="section-header">
91 <h2 class="section-title">热门主题</h2>
92 <el-button type="primary" text @click="$router.push('/forum/topics')">
93 查看全部 <el-icon><ArrowRight /></el-icon>
94 </el-button>
95 </div>
96 <div class="topics-list">
97 <div
98 v-for="topic in hotTopics"
99 :key="topic.id"
100 class="topic-item"
101 @click="navigateToTopic(topic.id)"
102 >
103 <div class="topic-content">
104 <div class="topic-header">
105 <h4 class="topic-title">{{ topic.title }}</h4>
106 <div class="topic-tags">
107 <el-tag
108 v-for="tag in topic.tags"
109 :key="tag"
110 size="small"
111 type="info"
112 >
113 {{ tag }}
114 </el-tag>
115 </div>
116 </div>
117 <div class="topic-meta">
118 <div class="author-info">
119 <el-avatar :size="24">{{ topic.author.charAt(0) }}</el-avatar>
120 <span class="author-name">{{ topic.author }}</span>
121 </div>
122 <div class="topic-stats">
123 <span class="stat-item">
124 <el-icon><View /></el-icon>
125 {{ topic.views }}
126 </span>
127 <span class="stat-item">
128 <el-icon><Comment /></el-icon>
129 {{ topic.replies }}
130 </span>
131 <span class="time">{{ formatTime(topic.lastReply) }}</span>
132 </div>
133 </div>
134 </div>
135 <div class="topic-status">
136 <el-tag v-if="topic.pinned" type="warning" size="small">置顶</el-tag>
137 <el-tag v-if="topic.hot" type="danger" size="small">热门</el-tag>
138 </div>
139 </div>
140 </div>
141 </div>
142
143 <!-- 最新回复 -->
144 <div class="recent-replies">
145 <h2 class="section-title">最新回复</h2>
146 <div class="replies-list">
147 <div
148 v-for="reply in recentReplies"
149 :key="reply.id"
150 class="reply-item"
151 @click="navigateToTopic(reply.topicId)"
152 >
153 <div class="reply-avatar">
154 <el-avatar :size="40">{{ reply.author.charAt(0) }}</el-avatar>
155 </div>
156 <div class="reply-content">
157 <div class="reply-header">
158 <span class="reply-author">{{ reply.author }}</span>
159 <span class="reply-action">回复了主题</span>
160 <span class="topic-title">{{ reply.topicTitle }}</span>
161 </div>
162 <div class="reply-text">{{ reply.content }}</div>
163 <div class="reply-time">{{ formatTime(reply.time) }}</div>
164 </div>
165 </div>
166 </div>
167 </div>
168 </div>
169
170 <!-- 发布新帖对话框 -->
171 <el-dialog
172 v-model="showNewTopicDialog"
173 title="发布新主题"
174 width="600px"
175 :before-close="handleCloseDialog"
176 >
177 <el-form
178 ref="topicFormRef"
179 :model="newTopic"
180 :rules="topicRules"
181 label-width="80px"
182 >
183 <el-form-item label="版块" prop="sectionId">
184 <el-select v-model="newTopic.sectionId" placeholder="选择版块">
185 <el-option
186 v-for="section in forumSections"
187 :key="section.id"
188 :label="section.name"
189 :value="section.id"
190 />
191 </el-select>
192 </el-form-item>
193
194 <el-form-item label="标题" prop="title">
195 <el-input
196 v-model="newTopic.title"
197 placeholder="请输入主题标题"
198 maxlength="100"
199 show-word-limit
200 />
201 </el-form-item>
202
203 <el-form-item label="标签">
204 <div class="tags-input">
205 <el-tag
206 v-for="tag in newTopic.tags"
207 :key="tag"
208 closable
209 @close="removeTopicTag(tag)"
210 >
211 {{ tag }}
212 </el-tag>
213 <el-input
214 v-if="tagInputVisible"
215 ref="tagInputRef"
216 v-model="tagInputValue"
217 size="small"
218 @keyup.enter="addTopicTag"
219 @blur="addTopicTag"
220 style="width: 100px;"
221 />
222 <el-button
223 v-else
224 size="small"
225 @click="showTagInput"
226 >
227 + 添加标签
228 </el-button>
229 </div>
230 </el-form-item>
231
232 <el-form-item label="内容" prop="content">
233 <el-input
234 v-model="newTopic.content"
235 type="textarea"
236 :rows="8"
237 placeholder="请输入主题内容..."
238 maxlength="5000"
239 show-word-limit
240 />
241 </el-form-item>
242 </el-form>
243
244 <template #footer>
245 <el-button @click="handleCloseDialog">取消</el-button>
246 <el-button type="primary" @click="submitNewTopic" :loading="submitting">
247 发布主题
248 </el-button>
249 </template>
250 </el-dialog>
251 </div>
252</template>
253
254<script>
255import { ref, reactive, onMounted, nextTick } from 'vue'
256import { useRouter } from 'vue-router'
257import { ElMessage, ElMessageBox } from 'element-plus'
258import {
259 Edit,
260 ChatDotRound,
261 Comment,
262 User,
263 View,
264 ArrowRight,
265 Film,
266 Headphones,
267 Monitor,
268 GamePad,
269 ChatLineRound,
270 QuestionFilled,
271 Bell
272} from '@element-plus/icons-vue'
208159515458d95702025-06-09 14:46:58 +0800273import {
274 getAllTopics,
275 getTopicsByForum,
276 createTopic,
277 searchTopics
278} from '@/api/topic'
279import {
280 getAllForums,
281 getForumById
282} from '@/api/forum'
283import Navbar from "@/components/Navbar.vue";
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800284
285export default {
286 name: 'ForumView',
208159515458d95702025-06-09 14:46:58 +0800287 components: {Navbar},
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800288 setup() {
289 const router = useRouter()
290 const topicFormRef = ref(null)
291 const tagInputRef = ref(null)
292
293 const showNewTopicDialog = ref(false)
294 const submitting = ref(false)
295 const tagInputVisible = ref(false)
296 const tagInputValue = ref('')
297
298 const forumStats = reactive({
208159515458d95702025-06-09 14:46:58 +0800299 totalTopics: 0,
300 totalReplies: 0,
301 activeUsers: 0,
302 todayPosts: 0
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800303 })
304
305 const newTopic = reactive({
306 sectionId: '',
307 title: '',
308 content: '',
309 tags: []
310 })
311
312 const topicRules = {
313 sectionId: [
314 { required: true, message: '请选择版块', trigger: 'change' }
315 ],
316 title: [
317 { required: true, message: '请输入标题', trigger: 'blur' },
318 { min: 5, max: 100, message: '标题长度在 5 到 100 个字符', trigger: 'blur' }
319 ],
320 content: [
321 { required: true, message: '请输入内容', trigger: 'blur' },
322 { min: 10, max: 5000, message: '内容长度在 10 到 5000 个字符', trigger: 'blur' }
323 ]
324 }
325
208159515458d95702025-06-09 14:46:58 +0800326 const forumSections = ref([])
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800327
208159515458d95702025-06-09 14:46:58 +0800328 const hotTopics = ref([])
329 const recentReplies = ref([])
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800330
208159515458d95702025-06-09 14:46:58 +0800331 const loadForumData = async () => {
332 try {
333 // 获取所有论坛
334 const forums = await getAllForums()
335 forumSections.value = forums.map(forum => ({
336 id: forum.id,
337 name: forum.name,
338 description: forum.description,
339 icon: getForumIcon(forum.name),
340 color: getForumColor(forum.name),
341 topics: forum.topicCount || 0,
342 replies: forum.replyCount || 0,
343 latestTopic: forum.latestTopic ? {
344 title: forum.latestTopic.title,
345 author: forum.latestTopic.user?.username || '匿名用户',
346 time: forum.latestTopic.createTime
347 } : null
348 }))
349
350 // 获取所有主题
351 const topics = await getAllTopics()
352 hotTopics.value = topics.slice(0, 3).map(topic => ({
353 id: topic.id,
354 title: topic.title,
355 author: topic.user?.username || '匿名用户',
356 views: topic.views || 0,
357 replies: topic.replies || 0,
358 lastReply: topic.lastReplyTime,
359 tags: topic.tags || [],
360 pinned: topic.pinned || false,
361 hot: topic.views > 1000
362 }))
363
364 // 更新统计数据
365 forumStats.totalTopics = topics.length
366 forumStats.totalReplies = topics.reduce((sum, topic) => sum + (topic.replies || 0), 0)
367 forumStats.activeUsers = new Set(topics.map(topic => topic.user?.id)).size
368 forumStats.todayPosts = topics.filter(topic => {
369 const topicDate = new Date(topic.createTime)
370 const today = new Date()
371 return topicDate.toDateString() === today.toDateString()
372 }).length
373
374 } catch (error) {
375 console.error('加载论坛数据失败:', error)
376 ElMessage.error('加载论坛数据失败')
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800377 }
208159515458d95702025-06-09 14:46:58 +0800378 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800379
208159515458d95702025-06-09 14:46:58 +0800380 const getForumIcon = (name) => {
381 const iconMap = {
382 '电影讨论': 'Film',
383 '音乐分享': 'Headphones',
384 '软件技术': 'Monitor',
385 '游戏天地': 'GamePad',
386 '站务公告': 'Bell',
387 '新手求助': 'QuestionFilled'
388 }
389 return iconMap[name] || 'ChatLineRound'
390 }
391
392 const getForumColor = (name) => {
393 const colorMap = {
394 '电影讨论': '#409eff',
395 '音乐分享': '#67c23a',
396 '软件技术': '#e6a23c',
397 '游戏天地': '#f56c6c',
398 '站务公告': '#909399',
399 '新手求助': '#606266'
400 }
401 return colorMap[name] || '#409eff'
402 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800403
404 const formatTime = (timeString) => {
405 const date = new Date(timeString)
406 const now = new Date()
407 const diff = now - date
408 const hours = Math.floor(diff / (1000 * 60 * 60))
409
410 if (hours < 1) return '刚刚'
411 if (hours < 24) return `${hours}小时前`
412 const days = Math.floor(hours / 24)
413 return `${days}天前`
414 }
415
416 const navigateToSection = (sectionId) => {
417 router.push(`/forum/section/${sectionId}`)
418 }
419
420 const navigateToTopic = (topicId) => {
421 router.push(`/forum/topic/${topicId}`)
422 }
423
424 const showTagInput = () => {
425 tagInputVisible.value = true
426 nextTick(() => {
427 tagInputRef.value?.focus()
428 })
429 }
430
431 const addTopicTag = () => {
432 const tag = tagInputValue.value.trim()
433 if (tag && !newTopic.tags.includes(tag)) {
434 newTopic.tags.push(tag)
435 }
436 tagInputVisible.value = false
437 tagInputValue.value = ''
438 }
439
440 const removeTopicTag = (tag) => {
441 const index = newTopic.tags.indexOf(tag)
442 if (index > -1) {
443 newTopic.tags.splice(index, 1)
444 }
445 }
446
447 const handleCloseDialog = () => {
448 if (newTopic.title || newTopic.content) {
449 ElMessageBox.confirm(
450 '确定要关闭吗?未保存的内容将会丢失。',
451 '提示',
452 {
453 confirmButtonText: '确定',
454 cancelButtonText: '取消',
455 type: 'warning'
456 }
457 ).then(() => {
458 resetForm()
459 showNewTopicDialog.value = false
460 }).catch(() => {
461 // 用户取消
462 })
463 } else {
464 resetForm()
465 showNewTopicDialog.value = false
466 }
467 }
468
469 const submitNewTopic = async () => {
470 try {
471 await topicFormRef.value?.validate()
472
473 submitting.value = true
474
208159515458d95702025-06-09 14:46:58 +0800475 const topicData = {
476 title: newTopic.title,
477 content: newTopic.content,
478 forumId: newTopic.sectionId,
479 tags: newTopic.tags
480 }
481
482 const response = await createTopic(topicData)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800483
484 ElMessage.success('主题发布成功!')
485 resetForm()
486 showNewTopicDialog.value = false
487
208159515458d95702025-06-09 14:46:58 +0800488 // 刷新论坛数据
489 await loadForumData()
490
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800491 // 跳转到新创建的主题页面
208159515458d95702025-06-09 14:46:58 +0800492 router.push(`/forum/topic/${response.id}`)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800493
494 } catch (error) {
208159515458d95702025-06-09 14:46:58 +0800495 console.error('发布主题失败:', error)
496 ElMessage.error('发布主题失败')
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800497 } finally {
498 submitting.value = false
499 }
500 }
501
502 const resetForm = () => {
503 topicFormRef.value?.resetFields()
504 newTopic.sectionId = ''
505 newTopic.title = ''
506 newTopic.content = ''
507 newTopic.tags = []
508 }
509
208159515458d95702025-06-09 14:46:58 +0800510 onMounted(() => {
511 loadForumData()
512 })
513
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800514 return {
515 showNewTopicDialog,
516 submitting,
517 tagInputVisible,
518 tagInputValue,
519 topicFormRef,
520 tagInputRef,
521 forumStats,
522 forumSections,
523 hotTopics,
524 recentReplies,
525 newTopic,
526 topicRules,
527 formatTime,
528 navigateToSection,
529 navigateToTopic,
530 showTagInput,
531 addTopicTag,
532 removeTopicTag,
533 handleCloseDialog,
534 submitNewTopic,
535 Edit,
536 ChatDotRound,
537 Comment,
538 User,
539 View,
540 ArrowRight,
541 Film,
542 Headphones,
543 Monitor,
544 GamePad,
545 Bell,
208159515458d95702025-06-09 14:46:58 +0800546 QuestionFilled,
547 loadForumData,
548 getForumIcon,
549 getForumColor
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800550 }
551 }
552}
553</script>
554
555<style lang="scss" scoped>
556.forum-page {
557 max-width: 1200px;
558 margin: 0 auto;
559 padding: 24px;
560 background: #f5f5f5;
561 min-height: 100vh;
562}
563
564.forum-header {
565 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
566 border-radius: 12px;
567 padding: 48px 32px;
568 margin-bottom: 24px;
569 color: white;
570 text-align: center;
571
572 h1 {
573 font-size: 36px;
574 font-weight: 600;
575 margin: 0 0 12px 0;
576 }
577
578 .header-description {
579 font-size: 18px;
580 margin: 0 0 24px 0;
581 opacity: 0.9;
582 }
583}
584
585.forum-stats {
586 background: #fff;
587 border-radius: 12px;
588 padding: 24px;
589 margin-bottom: 24px;
590 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
591
592 .stats-grid {
593 display: grid;
594 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
595 gap: 24px;
596
597 .stat-item {
598 display: flex;
599 align-items: center;
600 gap: 16px;
601
602 .stat-info {
603 h3 {
604 font-size: 24px;
605 font-weight: 600;
606 color: #2c3e50;
607 margin: 0 0 4px 0;
608 }
609
610 p {
611 font-size: 14px;
612 color: #7f8c8d;
613 margin: 0;
614 }
615 }
616 }
617 }
618}
619
620.forum-sections {
621 background: #fff;
622 border-radius: 12px;
623 padding: 24px;
624 margin-bottom: 24px;
625 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
626
627 .section-title {
628 font-size: 20px;
629 font-weight: 600;
630 color: #2c3e50;
631 margin: 0 0 20px 0;
632 }
633
634 .sections-list {
635 .section-card {
636 display: flex;
637 align-items: center;
638 gap: 20px;
639 padding: 20px;
640 border: 1px solid #f0f0f0;
641 border-radius: 8px;
642 margin-bottom: 12px;
643 cursor: pointer;
644 transition: all 0.3s ease;
645
646 &:hover {
647 background: #f8f9fa;
648 border-color: #409eff;
649 transform: translateX(4px);
650 }
651
652 .section-info {
653 flex: 1;
654
655 .section-name {
656 font-size: 18px;
657 font-weight: 600;
658 color: #2c3e50;
659 margin: 0 0 8px 0;
660 }
661
662 .section-description {
663 font-size: 14px;
664 color: #7f8c8d;
665 margin: 0 0 12px 0;
666 }
667
668 .section-stats {
669 display: flex;
670 gap: 16px;
671
672 .stat {
673 font-size: 12px;
674 color: #909399;
675 }
676 }
677 }
678
679 .section-latest {
680 width: 200px;
681
682 .latest-topic {
683 .topic-title {
684 font-size: 14px;
685 color: #2c3e50;
686 margin: 0 0 8px 0;
687 overflow: hidden;
688 text-overflow: ellipsis;
689 display: -webkit-box;
690 -webkit-line-clamp: 2;
691 -webkit-box-orient: vertical;
692 }
693
694 .topic-meta {
695 font-size: 12px;
696 color: #909399;
697
698 .author {
699 margin-right: 8px;
700 }
701 }
702 }
703 }
704 }
705 }
706}
707
708.hot-topics {
709 background: #fff;
710 border-radius: 12px;
711 padding: 24px;
712 margin-bottom: 24px;
713 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
714
715 .section-header {
716 display: flex;
717 justify-content: space-between;
718 align-items: center;
719 margin-bottom: 20px;
720
721 .section-title {
722 font-size: 20px;
723 font-weight: 600;
724 color: #2c3e50;
725 margin: 0;
726 }
727 }
728
729 .topics-list {
730 .topic-item {
731 display: flex;
732 justify-content: space-between;
733 align-items: center;
734 padding: 16px;
735 border: 1px solid #f0f0f0;
736 border-radius: 8px;
737 margin-bottom: 12px;
738 cursor: pointer;
739 transition: all 0.3s ease;
740
741 &:hover {
742 background: #f8f9fa;
743 border-color: #409eff;
744 }
745
746 .topic-content {
747 flex: 1;
748
749 .topic-header {
750 display: flex;
751 align-items: center;
752 gap: 12px;
753 margin-bottom: 8px;
754
755 .topic-title {
756 font-size: 16px;
757 font-weight: 500;
758 color: #2c3e50;
759 margin: 0;
760 }
761
762 .topic-tags {
763 .el-tag {
764 margin-right: 4px;
765 }
766 }
767 }
768
769 .topic-meta {
770 display: flex;
771 justify-content: space-between;
772 align-items: center;
773
774 .author-info {
775 display: flex;
776 align-items: center;
777 gap: 8px;
778
779 .author-name {
780 font-size: 14px;
781 color: #7f8c8d;
782 }
783 }
784
785 .topic-stats {
786 display: flex;
787 align-items: center;
788 gap: 16px;
789 font-size: 12px;
790 color: #909399;
791
792 .stat-item {
793 display: flex;
794 align-items: center;
795 gap: 4px;
796 }
797 }
798 }
799 }
800
801 .topic-status {
802 .el-tag {
803 margin-left: 8px;
804 }
805 }
806 }
807 }
808}
809
810.recent-replies {
811 background: #fff;
812 border-radius: 12px;
813 padding: 24px;
814 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
815
816 .section-title {
817 font-size: 20px;
818 font-weight: 600;
819 color: #2c3e50;
820 margin: 0 0 20px 0;
821 }
822
823 .replies-list {
824 .reply-item {
825 display: flex;
826 flex-direction: column;
827 gap: 12px;
828 padding: 16px;
829 border: 1px solid #f0f0f0;
830 border-radius: 8px;
831 margin-bottom: 12px;
832 cursor: pointer;
833 transition: all 0.3s ease;
834
835 &:hover {
836 background: #f8f9fa;
837 border-color: #409eff;
838 }
839
840 .reply-content {
841 flex: 1;
842
843 .reply-header {
844 font-size: 14px;
845 margin-bottom: 8px;
846
847 .reply-author {
848 font-weight: 600;
849 color: #2c3e50;
850 }
851
852 .reply-action {
853 color: #7f8c8d;
854 margin: 0 4px;
855 }
856
857 .topic-title {
858 color: #409eff;
859 font-weight: 500;
860 }
861 }
862
863 .reply-text {
864 font-size: 14px;
865 color: #5a6c7d;
866 margin-bottom: 8px;
867 overflow: hidden;
868 text-overflow: ellipsis;
869 display: -webkit-box;
870 -webkit-line-clamp: 2;
871 -webkit-box-orient: vertical;
872 }
873
874 .reply-time {
875 font-size: 12px;
876 color: #909399;
877 }
878 }
879 }
880 }
881}
882</style>
883
884.tags-input {
885 display: flex;
886 flex-wrap: wrap;
887 gap: 8px;
888 align-items: center;
889
890 .el-tag {
891 margin: 0;
892 }
893}
894
895@media (max-width: 768px) {
896 .forum-page {
897 padding: 16px;
898 }
899
900 .forum-header {
901 padding: 32px 24px;
902
903 h1 {
904 font-size: 28px;
905 }
906
907 .header-description {
908 font-size: 16px;
909 }
910 }
911
912 .stats-grid {
913 grid-template-columns: repeat(2, 1fr);
914 }
915
916 .section-card {
917 flex-direction: column;
918 text-align: center;
919
920 .section-latest {
921 width: 100%;
922 margin-top: 16px;
923 }
924 }
925
926 .topic-item {
927 flex-direction: column;
928 align-items: flex-start;
929
930 .topic-status {
931 margin-top: 12px;
932 align-self: flex-end;
933 }
934 }
xingjinwend652cc62025-06-04 19:52:19 +0800935}