blob: 3d559291d4afe278ee06f6c81c9fffacd1c8702e [file] [log] [blame]
Xing Jinwenff16b1e2025-06-05 00:29:26 +08001<template>
2 <div class="section-page">
208159515458d95702025-06-09 14:46:58 +08003 <Navbar />
Xing Jinwenff16b1e2025-06-05 00:29:26 +08004 <div class="page-container">
5 <!-- 面包屑导航 -->
6 <div class="breadcrumb">
7 <el-breadcrumb separator="/">
8 <el-breadcrumb-item :to="{ path: '/forum' }">论坛首页</el-breadcrumb-item>
9 <el-breadcrumb-item>{{ sectionInfo.name }}</el-breadcrumb-item>
10 </el-breadcrumb>
11 </div>
12
13 <!-- 版块信息 -->
14 <div class="section-header">
15 <div class="section-info">
16 <div class="section-icon">
17 <el-icon size="48" :color="sectionInfo.color">
18 <component :is="sectionInfo.icon" />
19 </el-icon>
20 </div>
21 <div class="section-details">
22 <h1 class="section-name">{{ sectionInfo.name }}</h1>
23 <p class="section-description">{{ sectionInfo.description }}</p>
24 <div class="section-stats">
25 <div class="stat-item">
26 <el-icon><ChatDotRound /></el-icon>
27 <span>{{ sectionInfo.topics }} 主题</span>
28 </div>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080029 </div>
30 </div>
31 </div>
32
33 <div class="section-actions">
34 <el-button type="primary" :icon="Edit" @click="showNewTopicDialog = true">
35 发布新主题
36 </el-button>
37 </div>
38 </div>
39
20815951548db5f2a2025-06-09 23:58:33 +080040<!-- &lt;!&ndash; 筛选和搜索 &ndash;&gt;-->
41<!-- <div class="filter-section">-->
42<!-- <div class="filter-left">-->
43<!-- <el-input-->
44<!-- v-model="searchQuery"-->
45<!-- placeholder="搜索主题..."-->
46<!-- :prefix-icon="Search"-->
47<!-- @keyup.enter="handleSearch"-->
48<!-- clearable-->
49<!-- style="width: 300px;"-->
50<!-- />-->
51<!-- <el-button type="primary" @click="handleSearch">搜索</el-button>-->
52<!-- </div>-->
53<!-- -->
54<!-- <div class="filter-right">-->
55<!-- <el-select v-model="sortBy" placeholder="排序方式" @change="handleFilter">-->
56<!-- <el-option label="最新回复" value="last_reply" />-->
57<!-- <el-option label="发布时间" value="create_time" />-->
58<!-- <el-option label="回复数量" value="replies" />-->
59<!-- <el-option label="浏览次数" value="views" />-->
60<!-- </el-select>-->
61<!-- -->
62<!-- <el-select v-model="filterType" placeholder="主题类型" @change="handleFilter">-->
63<!-- <el-option label="全部主题" value="" />-->
64<!-- <el-option label="置顶主题" value="pinned" />-->
65<!-- <el-option label="热门主题" value="hot" />-->
66<!-- <el-option label="精华主题" value="featured" />-->
67<!-- </el-select>-->
68<!-- </div>-->
69<!-- </div>-->
Xing Jinwenff16b1e2025-06-05 00:29:26 +080070
71 <!-- 置顶主题 -->
20815951548db5f2a2025-06-09 23:58:33 +080072<!-- <div v-if="pinnedTopics.length > 0" class="pinned-topics">-->
73<!-- <h3 class="section-title">置顶主题</h3>-->
74<!-- <div class="topics-list">-->
75<!-- <div-->
76<!-- v-for="topic in pinnedTopics"-->
77<!-- :key="topic.id"-->
78<!-- class="topic-item pinned"-->
79<!-- @click="navigateToTopic(topic.id)"-->
80<!-- >-->
81<!-- <div class="topic-status">-->
82<!-- <el-icon class="pin-icon"><Top /></el-icon>-->
83<!-- </div>-->
84<!-- -->
85<!-- <div class="topic-content">-->
86<!-- <div class="topic-header">-->
87<!-- <h4 class="topic-title">{{ topic.title }}</h4>-->
88<!-- <div class="topic-tags">-->
89<!-- <el-tag type="warning" size="small">置顶</el-tag>-->
90<!-- <el-tag v-if="topic.hot" type="danger" size="small">热门</el-tag>-->
91<!-- <el-tag v-if="topic.featured" type="success" size="small">精华</el-tag>-->
92<!-- </div>-->
93<!-- </div>-->
94<!-- -->
95<!-- <div class="topic-meta">-->
96<!-- <div class="author-info">-->
97<!-- <el-avatar :size="24">{{ topic.user?.username ? topic.user.username.charAt(0) : 'A' }}</el-avatar>-->
98<!-- <span class="author-name">{{ topic.user?.username || '匿名' }}</span>-->
99<!-- <span class="create-time">{{ formatTime(topic.createTime) }}</span>-->
100<!-- </div>-->
101<!-- -->
102<!-- <div class="topic-stats">-->
103<!-- <span class="stat-item">-->
104<!-- <el-icon><View /></el-icon>-->
105<!-- {{ topic.views }}-->
106<!-- </span>-->
107<!-- <span class="stat-item">-->
108<!-- <el-icon><Comment /></el-icon>-->
109<!-- {{ topic.replies }}-->
110<!-- </span>-->
111<!-- </div>-->
112<!-- </div>-->
113<!-- </div>-->
114<!-- -->
115<!-- <div class="last-reply">-->
116<!-- <div v-if="topic.lastReply" class="reply-info">-->
117<!-- <div class="reply-author">{{ topic.lastReply.author }}</div>-->
118<!-- <div class="reply-time">{{ formatTime(topic.lastReply.time) }}</div>-->
119<!-- </div>-->
120<!-- </div>-->
121<!-- </div>-->
122<!-- </div>-->
123<!-- </div>-->
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800124
125 <!-- 普通主题列表 -->
126 <div class="normal-topics">
127 <div class="section-header">
128 <h3 class="section-title">主题列表</h3>
129 <div class="results-info">
130 共 {{ totalTopics }} 个主题
131 </div>
132 </div>
133
134 <div class="topics-list" v-loading="loading">
135 <div
136 v-for="topic in topics"
137 :key="topic.id"
138 class="topic-item"
139 @click="navigateToTopic(topic.id)"
140 >
141 <div class="topic-status">
142 <el-icon v-if="topic.hasNewReplies" class="new-icon" color="#f56c6c">
143 <ChatDotRound />
144 </el-icon>
145 <el-icon v-else class="normal-icon" color="#909399">
146 <ChatLineRound />
147 </el-icon>
148 </div>
149
150 <div class="topic-content">
151 <div class="topic-header">
152 <h4 class="topic-title">{{ topic.title }}</h4>
153 <div class="topic-tags">
20815951548db5f2a2025-06-09 23:58:33 +0800154 <el-tag v-if="topic.isPinned" type="warning" size="small">置顶</el-tag>
155 <el-tag v-if="topic.views > 1000" type="danger" size="small">热门</el-tag>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800156 <el-tag v-if="topic.featured" type="success" size="small">精华</el-tag>
20815951548db5f2a2025-06-09 23:58:33 +0800157 <el-tag v-if="topic.isLocked" type="info" size="small">已关闭</el-tag>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800158 </div>
159 </div>
160
161 <div class="topic-meta">
162 <div class="author-info">
208159515458d95702025-06-09 14:46:58 +0800163 <el-avatar :size="24">{{ topic.user?.username ? topic.user.username.charAt(0) : 'A' }}</el-avatar>
164 <span class="author-name">{{ topic.user?.username || '匿名' }}</span>
20815951548db5f2a2025-06-09 23:58:33 +0800165<!-- <span class="create-time">{{ formatTime(topic.createdAt) }}</span>-->
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800166 </div>
167
20815951548db5f2a2025-06-09 23:58:33 +0800168<!-- <div class="topic-stats">-->
169<!-- <span class="stat-item">-->
170<!-- <el-icon><View /></el-icon>-->
171<!-- {{ topic.views || 0 }}-->
172<!-- </span>-->
173<!-- <span class="stat-item">-->
174<!-- <el-icon><Comment /></el-icon>-->
175<!-- {{ topic.replies || 0 }}-->
176<!-- </span>-->
177<!-- </div>-->
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800178 </div>
179 </div>
180
181 <div class="last-reply">
20815951548db5f2a2025-06-09 23:58:33 +0800182 <div v-if="topic.updatedAt" class="reply-info">
183 <div class="reply-author">最后回复</div>
184 <div class="reply-time">{{ formatTime(topic.updatedAt) }}</div>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800185 </div>
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800186 </div>
187 </div>
188
189 <div v-if="topics.length === 0 && !loading" class="no-topics">
190 暂无主题,快来发布第一个主题吧!
191 </div>
192 </div>
193
194 <!-- 分页 -->
195 <div class="pagination-wrapper">
196 <el-pagination
208159515458d95702025-06-09 14:46:58 +0800197 v-model="currentPage"
198 :page-size="pageSize"
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800199 :page-sizes="[20, 50, 100]"
200 :total="totalTopics"
201 layout="total, sizes, prev, pager, next, jumper"
202 @size-change="handleSizeChange"
203 @current-change="handleCurrentChange"
204 />
205 </div>
206 </div>
207 </div>
208
209 <!-- 发布新主题对话框 -->
210 <el-dialog
211 v-model="showNewTopicDialog"
212 title="发布新主题"
213 width="600px"
214 :before-close="handleCloseDialog"
215 >
216 <el-form
217 ref="topicFormRef"
218 :model="newTopic"
219 :rules="topicRules"
220 label-width="80px"
221 >
222 <el-form-item label="主题标题" prop="title">
223 <el-input
224 v-model="newTopic.title"
225 placeholder="请输入主题标题"
226 maxlength="100"
227 show-word-limit
228 />
229 </el-form-item>
230
231 <el-form-item label="主题标签">
232 <div class="tags-input">
233 <el-tag
234 v-for="tag in newTopic.tags"
235 :key="tag"
236 closable
237 @close="removeTopicTag(tag)"
238 >
239 {{ tag }}
240 </el-tag>
241 <el-input
242 v-if="tagInputVisible"
243 ref="tagInputRef"
244 v-model="tagInputValue"
245 size="small"
246 @keyup.enter="addTopicTag"
247 @blur="addTopicTag"
248 style="width: 100px;"
249 />
250 <el-button
251 v-else
252 size="small"
253 @click="showTagInput"
254 >
255 + 添加标签
256 </el-button>
257 </div>
258 </el-form-item>
259
260 <el-form-item label="主题内容" prop="content">
261 <el-input
262 v-model="newTopic.content"
263 type="textarea"
264 :rows="8"
265 placeholder="请输入主题内容..."
266 maxlength="5000"
267 show-word-limit
268 />
269 </el-form-item>
270
271 <el-form-item label="主题选项">
272 <el-checkbox-group v-model="newTopic.options">
273 <el-checkbox label="hot">申请热门</el-checkbox>
274 <el-checkbox label="featured">申请精华</el-checkbox>
275 </el-checkbox-group>
276 </el-form-item>
277 </el-form>
278
279 <template #footer>
280 <el-button @click="handleCloseDialog">取消</el-button>
281 <el-button type="primary" @click="submitNewTopic" :loading="submitting">
282 发布主题
283 </el-button>
284 </template>
285 </el-dialog>
286 </div>
287</template>
288
289<script>
290import { ref, reactive, onMounted, nextTick } from 'vue'
291import { useRoute, useRouter } from 'vue-router'
292import { ElMessage, ElMessageBox } from 'element-plus'
293import {
294 Edit,
295 Search,
296 ChatDotRound,
297 Comment,
298 User,
299 View,
300 Top,
301 ChatLineRound,
302 Film,
303 Headphones,
304 Monitor,
305 GamePad,
306 Bell,
20815951548db5f2a2025-06-09 23:58:33 +0800307 QuestionFilled,
308 Plus
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800309} from '@element-plus/icons-vue'
208159515458d95702025-06-09 14:46:58 +0800310import { getTopicsByForum, createTopic } from '@/api/topic'
20815951548db5f2a2025-06-09 23:58:33 +0800311import { getForumById } from '@/api/forum'
208159515458d95702025-06-09 14:46:58 +0800312import Navbar from "@/components/Navbar.vue";
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800313
314export default {
315 name: 'ForumSectionView',
208159515458d95702025-06-09 14:46:58 +0800316 components: {Navbar},
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800317 setup() {
318 const route = useRoute()
319 const router = useRouter()
320 const topicFormRef = ref(null)
321 const tagInputRef = ref(null)
322
323 const loading = ref(false)
324 const showNewTopicDialog = ref(false)
325 const submitting = ref(false)
326 const tagInputVisible = ref(false)
327 const tagInputValue = ref('')
328
329 const searchQuery = ref('')
330 const sortBy = ref('last_reply')
331 const filterType = ref('')
332 const currentPage = ref(1)
333 const pageSize = ref(20)
334 const totalTopics = ref(0)
335
20815951548db5f2a2025-06-09 23:58:33 +0800336 const sectionInfo = ref({})
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800337
338 const newTopic = reactive({
339 title: '',
340 content: '',
341 tags: [],
342 options: []
343 })
344
345 const topicRules = {
346 title: [
347 { required: true, message: '请输入标题', trigger: 'blur' },
348 { min: 5, max: 100, message: '标题长度在 5 到 100 个字符', trigger: 'blur' }
349 ],
350 content: [
351 { required: true, message: '请输入内容', trigger: 'blur' },
352 { min: 10, max: 5000, message: '内容长度在 10 到 5000 个字符', trigger: 'blur' }
353 ]
354 }
355
356 const pinnedTopics = ref([
357 {
358 id: 1,
359 title: '【公告】本版块发帖规则和注意事项',
360 author: 'Admin',
361 createTime: '2025-05-01T10:00:00',
362 views: 5678,
363 replies: 23,
364 hot: false,
365 featured: true,
366 lastReply: {
367 author: 'User123',
368 time: '2025-06-02T15:30:00'
369 }
370 }
371 ])
372
208159515458d95702025-06-09 14:46:58 +0800373 const topics = ref([])
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800374
375 onMounted(() => {
376 const sectionId = route.params.id
377 fetchSectionData(sectionId)
378 })
379
380 const fetchSectionData = async (id) => {
381 loading.value = true
382 try {
20815951548db5f2a2025-06-09 23:58:33 +0800383 console.log('🏁 fetchSectionData 开始,id:', id)
384 const res = await getForumById(id)
385 console.log('📥 getForumById 响应:', res)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800386
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800387 sectionInfo.value = {
20815951548db5f2a2025-06-09 23:58:33 +0800388 ...res,
389 icon: getForumIcon(res.name),
390 color: getForumColor(res.name),
391 topics: 0
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800392 }
393
20815951548db5f2a2025-06-09 23:58:33 +0800394 console.log('📋 sectionInfo 设置完成:', sectionInfo.value)
395 console.log('🚀 准备调用 fetchTopics')
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800396
20815951548db5f2a2025-06-09 23:58:33 +0800397 // sectionInfo有数据后再请求主题列表
398 fetchTopics()
399
400 console.log('✅ fetchTopics 调用完成')
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800401 } catch (error) {
20815951548db5f2a2025-06-09 23:58:33 +0800402 console.error('❌ fetchSectionData 错误:', error)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800403 ElMessage.error('获取版块数据失败')
404 } finally {
405 loading.value = false
406 }
407 }
408
409 const formatTime = (timeString) => {
20815951548db5f2a2025-06-09 23:58:33 +0800410 // 处理Java LocalDateTime数组格式: [年, 月, 日, 时, 分, 秒, 纳秒]
411 if (Array.isArray(timeString) && timeString.length >= 6) {
412 const [year, month, day, hour, minute, second] = timeString
413 const date = new Date(year, month - 1, day, hour, minute, second) // 月份需要减1
414 const now = new Date()
415 const diff = now - date
416 const hours = Math.floor(diff / (1000 * 60 * 60))
417
418 if (hours < 1) return '刚刚'
419 if (hours < 24) return `${hours}小时前`
420 const days = Math.floor(hours / 24)
421 if (days < 7) return `${days}天前`
422
423 return date.toLocaleDateString('zh-CN', {
424 month: '2-digit',
425 day: '2-digit',
426 hour: '2-digit',
427 minute: '2-digit'
428 })
429 }
430
431 // 处理普通字符串格式
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800432 const date = new Date(timeString)
20815951548db5f2a2025-06-09 23:58:33 +0800433 if (isNaN(date.getTime())) return '时间未知'
434
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800435 const now = new Date()
436 const diff = now - date
437 const hours = Math.floor(diff / (1000 * 60 * 60))
438
439 if (hours < 1) return '刚刚'
440 if (hours < 24) return `${hours}小时前`
441 const days = Math.floor(hours / 24)
442 if (days < 7) return `${days}天前`
443
444 return date.toLocaleDateString('zh-CN', {
445 month: '2-digit',
446 day: '2-digit',
447 hour: '2-digit',
448 minute: '2-digit'
449 })
450 }
451
452 const navigateToTopic = (topicId) => {
453 router.push(`/forum/topic/${topicId}`)
454 }
455
456 const handleSearch = () => {
457 currentPage.value = 1
458 fetchTopics()
459 }
460
461 const handleFilter = () => {
462 currentPage.value = 1
463 fetchTopics()
464 }
465
466 const fetchTopics = async () => {
467 loading.value = true
468 try {
20815951548db5f2a2025-06-09 23:58:33 +0800469 // 调试信息:确认方法被调用和参数值
470 console.log('🔍 fetchTopics 被调用,sectionInfo.value:', sectionInfo.value)
471 console.log('🔍 sectionInfo.value.id:', sectionInfo.value.id)
472
473 if (!sectionInfo.value.id) {
474 console.error('❌ sectionInfo.value.id 为空,无法请求主题列表')
475 return
476 }
477
208159515458d95702025-06-09 14:46:58 +0800478 // 调用后端API获取主题列表
20815951548db5f2a2025-06-09 23:58:33 +0800479 console.log('🚀 正在请求主题列表,forumId:', sectionInfo.value.id)
208159515458d95702025-06-09 14:46:58 +0800480 const res = await getTopicsByForum(sectionInfo.value.id)
20815951548db5f2a2025-06-09 23:58:33 +0800481 console.log('✅ 主题列表响应:', res)
482
208159515458d95702025-06-09 14:46:58 +0800483 topics.value = res.data || res // 兼容不同返回结构
484 totalTopics.value = topics.value.length
485 // 同时更新顶部显示的主题数量
486 sectionInfo.value.topics = topics.value.length
20815951548db5f2a2025-06-09 23:58:33 +0800487
488 console.log('✅ topics.value:', topics.value)
489 console.log('✅ totalTopics.value:', totalTopics.value)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800490 } catch (error) {
20815951548db5f2a2025-06-09 23:58:33 +0800491 console.error('❌ fetchTopics 错误:', error)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800492 ElMessage.error('获取主题列表失败')
493 } finally {
494 loading.value = false
495 }
496 }
497
498 const handleSizeChange = (size) => {
499 pageSize.value = size
500 currentPage.value = 1
501 fetchTopics()
502 }
503
504 const handleCurrentChange = (page) => {
505 currentPage.value = page
506 fetchTopics()
507 }
508
509 const showTagInput = () => {
510 tagInputVisible.value = true
511 nextTick(() => {
512 tagInputRef.value?.focus()
513 })
514 }
515
516 const addTopicTag = () => {
517 const tag = tagInputValue.value.trim()
518 if (tag && !newTopic.tags.includes(tag)) {
519 newTopic.tags.push(tag)
520 }
521 tagInputVisible.value = false
522 tagInputValue.value = ''
523 }
524
525 const removeTopicTag = (tag) => {
526 const index = newTopic.tags.indexOf(tag)
527 if (index > -1) {
528 newTopic.tags.splice(index, 1)
529 }
530 }
531
532 const handleCloseDialog = () => {
533 if (newTopic.title || newTopic.content) {
534 ElMessageBox.confirm(
535 '确定要关闭吗?未保存的内容将会丢失。',
536 '提示',
537 {
538 confirmButtonText: '确定',
539 cancelButtonText: '取消',
540 type: 'warning'
541 }
542 ).then(() => {
543 resetForm()
544 showNewTopicDialog.value = false
545 }).catch(() => {
546 // 用户取消
547 })
548 } else {
549 resetForm()
550 showNewTopicDialog.value = false
551 }
552 }
553
554 const submitNewTopic = async () => {
555 try {
556 await topicFormRef.value?.validate()
557
558 submitting.value = true
559
208159515458d95702025-06-09 14:46:58 +0800560 // 构建主题数据
561 const topicData = {
562 title: newTopic.title,
563 content: newTopic.content,
564 forumId: sectionInfo.value.id, // 使用当前版块ID
565 tags: newTopic.tags,
566 isPinned: newTopic.options.includes('hot'),
567 isLocked: false
568 }
569
570 console.log('提交主题数据:', topicData)
571
572 // 调用API创建主题
573 const response = await createTopic(topicData)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800574
575 ElMessage.success('主题发布成功!')
576 resetForm()
577 showNewTopicDialog.value = false
578
579 // 刷新主题列表
580 fetchTopics()
581
582 } catch (error) {
208159515458d95702025-06-09 14:46:58 +0800583 console.error('发布主题失败:', error)
584 ElMessage.error('发布主题失败,请重试')
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800585 } finally {
586 submitting.value = false
587 }
588 }
589
590 const resetForm = () => {
591 topicFormRef.value?.resetFields()
592 newTopic.title = ''
593 newTopic.content = ''
594 newTopic.tags = []
595 newTopic.options = []
596 }
597
20815951548db5f2a2025-06-09 23:58:33 +0800598 // icon和color映射函数,和首页保持一致
599 const getForumIcon = (name) => {
600 const iconMap = {
601 '电影讨论': 'Film',
602 '音乐分享': 'Headphones',
603 '软件技术': 'Monitor',
604 '游戏天地': 'GamePad',
605 '站务公告': 'Bell',
606 '新手求助': 'QuestionFilled'
607 }
608 return iconMap[name] || 'ChatLineRound'
609 }
610 const getForumColor = (name) => {
611 const colorMap = {
612 '电影讨论': '#409eff',
613 '音乐分享': '#67c23a',
614 '软件技术': '#e6a23c',
615 '游戏天地': '#f56c6c',
616 '站务公告': '#909399',
617 '新手求助': '#606266'
618 }
619 return colorMap[name] || '#409eff'
620 }
621
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800622 return {
623 loading,
624 showNewTopicDialog,
625 submitting,
626 tagInputVisible,
627 tagInputValue,
628 searchQuery,
629 sortBy,
630 filterType,
631 currentPage,
632 pageSize,
633 totalTopics,
634 sectionInfo,
635 pinnedTopics,
636 topics,
637 newTopic,
638 topicRules,
639 topicFormRef,
640 tagInputRef,
641 formatTime,
642 navigateToTopic,
643 handleSearch,
644 handleFilter,
645 handleSizeChange,
646 handleCurrentChange,
647 showTagInput,
648 addTopicTag,
649 removeTopicTag,
650 handleCloseDialog,
651 submitNewTopic,
652 Edit,
653 Search,
654 ChatDotRound,
655 Comment,
656 User,
657 View,
658 Top,
659 ChatLineRound,
660 Film,
661 Headphones,
662 Monitor,
663 GamePad,
664 Bell,
20815951548db5f2a2025-06-09 23:58:33 +0800665 QuestionFilled,
666 Plus
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800667 }
668 }
669}
670</script>
671
672<style lang="scss" scoped>
673.section-page {
674 max-width: 1200px;
675 margin: 0 auto;
676 padding: 24px;
677 background: #f5f5f5;
678 min-height: 100vh;
679}
680
681.breadcrumb {
682 margin-bottom: 16px;
683}
684
685.section-header {
686 background: #fff;
687 border-radius: 12px;
688 padding: 32px;
689 margin-bottom: 24px;
690 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
691
692 display: flex;
693 justify-content: space-between;
694 align-items: center;
695 gap: 24px;
696
697 .section-info {
698 display: flex;
699 align-items: center;
700 gap: 20px;
701 flex: 1;
702
703 .section-details {
704 .section-name {
705 font-size: 28px;
706 font-weight: 600;
707 color: #2c3e50;
708 margin: 0 0 8px 0;
709 }
710
711 .section-description {
712 font-size: 16px;
713 color: #7f8c8d;
714 margin: 0 0 16px 0;
715 }
716
717 .section-stats {
718 display: flex;
719 gap: 24px;
720
721 .stat-item {
722 display: flex;
723 align-items: center;
724 gap: 8px;
725 font-size: 14px;
726 color: #606266;
727 }
728 }
729 }
730 }
731
732 .section-actions {
733 flex-shrink: 0;
734 }
735}
736
737.filter-section {
738 background: #fff;
739 border-radius: 12px;
740 padding: 20px 24px;
741 margin-bottom: 24px;
742 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
743
744 display: flex;
745 justify-content: space-between;
746 align-items: center;
747 gap: 20px;
748
749 .filter-left {
750 display: flex;
751 align-items: center;
752 gap: 12px;
753 }
754
755 .filter-right {
756 display: flex;
757 align-items: center;
758 gap: 12px;
759
760 .el-select {
761 width: 120px;
762 }
763 }
764}
765
766.pinned-topics, .normal-topics {
767 background: #fff;
768 border-radius: 12px;
769 padding: 24px;
770 margin-bottom: 24px;
771 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
772
773 .section-header {
774 display: flex;
775 justify-content: space-between;
776 align-items: center;
777 margin-bottom: 20px;
778 background: none;
779 padding: 0;
780 box-shadow: none;
781
782 .section-title {
783 font-size: 18px;
784 font-weight: 600;
785 color: #2c3e50;
786 margin: 0;
787 }
788
789 .results-info {
790 font-size: 14px;
791 color: #909399;
792 }
793 }
794}
795
796.topics-list {
797 .topic-item {
798 display: flex;
799 align-items: center;
800 gap: 16px;
801 padding: 16px;
802 border: 1px solid #f0f0f0;
803 border-radius: 8px;
804 margin-bottom: 12px;
805 cursor: pointer;
806 transition: all 0.3s ease;
807
808 &:hover {
809 background: #f8f9fa;
810 border-color: #409eff;
811 transform: translateX(2px);
812 }
813
814 &.pinned {
815 background: linear-gradient(90deg, #fff7e6 0%, #fff 100%);
816 border-color: #e6a23c;
817 }
818
819 .topic-status {
820 width: 32px;
821 text-align: center;
822
823 .pin-icon {
824 color: #e6a23c;
825 }
826
827 .new-icon {
828 animation: pulse 2s infinite;
829 }
830 }
831
832 .topic-content {
833 flex: 1;
834
835 .topic-header {
836 display: flex;
837 align-items: center;
838 gap: 12px;
839 margin-bottom: 8px;
840
841 .topic-title {
842 font-size: 16px;
843 font-weight: 500;
844 color: #2c3e50;
845 margin: 0;
846 flex: 1;
847
848 &:hover {
849 color: #409eff;
850 }
851 }
852
853 .topic-tags {
854 .el-tag {
855 margin-left: 4px;
856 }
857 }
858 }
859
860 .topic-meta {
861 display: flex;
862 justify-content: space-between;
863 align-items: center;
864
865 .author-info {
866 display: flex;
867 align-items: center;
868 gap: 8px;
869
870 .author-name {
871 font-size: 14px;
872 font-weight: 500;
873 color: #606266;
874 }
875
876 .create-time {
877 font-size: 12px;
878 color: #909399;
879 }
880 }
881
882 .topic-stats {
883 display: flex;
884 gap: 16px;
885
886 .stat-item {
887 display: flex;
888 align-items: center;
889 gap: 4px;
890 font-size: 12px;
891 color: #909399;
892 }
893 }
894 }
895 }
896
897 .last-reply {
898 width: 150px;
899 text-align: right;
900
901 .reply-info {
902 .reply-author {
903 font-size: 14px;
904 font-weight: 500;
905 color: #606266;
906 margin-bottom: 4px;
907 }
908
909 .reply-time {
910 font-size: 12px;
911 color: #909399;
912 }
913 }
914
915 .no-reply {
916 font-size: 12px;
917 color: #c0c4cc;
918 }
919 }
920 }
921
922 .no-topics {
923 text-align: center;
924 color: #909399;
925 padding: 60px 0;
926 font-size: 16px;
927 }
928}
929
930.pagination-wrapper {
931 margin-top: 24px;
932 text-align: center;
933}
934
935.tags-input {
936 display: flex;
937 flex-wrap: wrap;
938 gap: 8px;
939 align-items: center;
940
941 .el-tag {
942 margin: 0;
943 }
944}
945
946@keyframes pulse {
947 0% {
948 transform: scale(1);
949 }
950 50% {
951 transform: scale(1.1);
952 }
953 100% {
954 transform: scale(1);
955 }
956}
957
958@media (max-width: 768px) {
959 .section-page {
960 padding: 16px;
961 }
962
963 .section-header {
964 flex-direction: column;
965 align-items: flex-start;
966 gap: 16px;
967
968 .section-info {
969 flex-direction: column;
970 text-align: center;
971
972 .section-stats {
973 justify-content: center;
974 }
975 }
976
977 .section-actions {
978 width: 100%;
979 text-align: center;
980 }
981 }
982
983 .filter-section {
984 flex-direction: column;
985 gap: 16px;
986
987 .filter-left, .filter-right {
988 width: 100%;
989 justify-content: center;
990 }
991
992 .filter-right {
993 .el-select {
994 width: 140px;
995 }
996 }
997 }
998
999 .topic-item {
1000 flex-direction: column;
1001 align-items: flex-start;
1002 gap: 12px;
1003
1004 .topic-status {
1005 align-self: flex-start;
1006 }
1007
1008 .topic-content {
1009 width: 100%;
1010
1011 .topic-meta {
1012 flex-direction: column;
1013 align-items: flex-start;
1014 gap: 8px;
1015 }
1016 }
1017
1018 .last-reply {
1019 width: 100%;
1020 text-align: left;
1021 }
1022 }
1023}
xingjinwend652cc62025-06-04 19:52:19 +08001024</style>