Files
GVSite/src/views/community/CommunityPostView.vue
T

226 lines
8.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="gvs-community">
<div class="gvs-container gvs-page-top">
<button
class="gvs-btn gvs-btn-ghost mb-4!"
@click="router.back()"
>
<i class="fa fa-arrow-left"></i> 返回
</button>
<div v-if="loading" class="gvs-card p-16 text-center text-gray-400">
<i class="fa fa-spinner fa-spin text-2xl"></i>
</div>
<template v-else-if="post">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 items-start">
<!-- 帖子主体 -->
<div class="lg:col-span-2 space-y-4">
<div class="gvs-card">
<div class="p-6">
<div class="flex items-center gap-2 mb-3 flex-wrap">
<span class="gvs-tag">{{ post.BoardName }}</span>
<span v-if="post.CategoryName" class="gvs-tag gray">{{ post.CategoryName }}</span>
<span v-if="post.IsPinned" class="gvs-tag red">置顶</span>
<span v-if="post.IsLocked" class="gvs-tag gray">已锁定</span>
</div>
<h1 class="text-xl font-bold text-gray-900 leading-snug">{{ post.Title }}</h1>
<div class="flex items-center gap-4 mt-4 pb-4 border-b border-gray-100">
<div class="gvs-feed-avatar w-9! h-9!">
<i class="fa fa-user"></i>
<img v-if="post.AuthorAvatar" :src="post.AuthorAvatar" class="gvs-avatar-img" alt="" @error="hideAvatar" />
</div>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-gray-900">{{ post.AuthorName || '匿名' }}</div>
<div class="text-xs text-gray-400 mt-0.5">{{ post.CreateTime }}</div>
</div>
<span class="text-xs text-gray-400 flex items-center gap-1"><i class="fa fa-eye"></i> {{ post.ViewCount }} 浏览</span>
</div>
<div class="mt-5 text-gray-800 leading-7 whitespace-pre-wrap min-h-24 text-[14px]">
{{ post.Content }}
</div>
<div class="mt-6 pt-4 border-t border-gray-100 flex items-center justify-between">
<button
class="gvs-btn"
:class="liked ? 'gvs-btn-primary' : 'gvs-btn-ghost'"
@click="toggleLike"
>
<i class="fa" :class="liked ? 'fa-solid fa-thumbs-up' : 'fa-regular fa-thumbs-up'"></i>
{{ liked ? '已赞' : '点赞' }} ({{ post.LikeCount }})
</button>
<span class="text-xs text-gray-400">{{ post.ReplyCount }} 条回复</span>
</div>
</div>
</div>
<!-- 回复列表 -->
<div class="gvs-card">
<div class="gvs-card-hd">
<i class="fa fa-comments"></i> 全部回复 ({{ post.Replies.length }})
</div>
<div v-if="post.Replies.length" class="divide-y divide-gray-100">
<div v-for="(r, i) in post.Replies" :key="r.ReplyUUID" class="px-6 py-4 flex gap-3">
<div class="gvs-feed-avatar w-8! h-8! text-sm!">
<i class="fa fa-user"></i>
<img v-if="r.AuthorAvatar" :src="r.AuthorAvatar" class="gvs-avatar-img" alt="" @error="hideAvatar" />
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2">
<span class="font-medium text-sm text-gray-900">{{ r.AuthorName || '匿名' }}</span>
<span class="text-xs text-gray-400">#{{ i + 1 }}</span>
<span class="text-xs text-gray-400 ml-auto">{{ r.CreateTime }}</span>
</div>
<p class="mt-1.5 text-gray-700 text-sm leading-relaxed whitespace-pre-wrap">{{ r.Content }}</p>
</div>
</div>
</div>
<div v-else class="px-6 py-12 text-center text-gray-400 text-sm">暂无回复,抢沙发!</div>
</div>
<!-- 回复框 -->
<div class="gvs-card p-6">
<div v-if="!isLoggedIn" class="text-center text-sm text-gray-500 py-4">
请先
<a href="https://gerp.gvsds.com/#/login" target="_blank" class="text-blue-600 hover:underline">登录</a>
后参与讨论
</div>
<template v-else>
<div v-if="post.IsLocked" class="text-center text-sm text-gray-400 py-4">该帖子已锁定,不可回复</div>
<template v-else>
<textarea
v-model="replyContent"
class="gvs-textarea"
rows="3"
placeholder="友善发言,理性讨论..."
></textarea>
<div class="flex justify-end mt-3">
<button
class="gvs-btn gvs-btn-primary"
:disabled="submitting || !replyContent.trim()"
@click="submitReply"
>
{{ submitting ? '提交中...' : '发表回复' }}
</button>
</div>
</template>
</template>
</div>
</div>
<!-- 侧栏 -->
<div class="space-y-4">
<div class="gvs-card">
<div class="gvs-card-hd"><i class="fa fa-info-circle"></i> 帖子信息</div>
<div class="gvs-side-list">
<div class="gvs-side-item">
<span class="gvs-side-num"><i class="fa fa-th-large"></i></span>
板块:{{ post.BoardName }}
</div>
<div class="gvs-side-item">
<span class="gvs-side-num"><i class="fa fa-eye"></i></span>
{{ post.ViewCount }} 次浏览
</div>
<div class="gvs-side-item">
<span class="gvs-side-num"><i class="fa fa-thumbs-up"></i></span>
{{ post.LikeCount }} 次点赞
</div>
<div class="gvs-side-item">
<span class="gvs-side-num"><i class="fa fa-reply"></i></span>
{{ post.ReplyCount }} 条回复
</div>
</div>
</div>
<div class="gvs-card">
<div class="gvs-card-hd"><i class="fa fa-gavel"></i> 社区规范</div>
<div class="gvs-side-list text-xs text-gray-500 leading-6">
<p>· 请友善发言,理性讨论</p>
<p>· 禁止发布违法违规内容</p>
<p>· 提问前建议先搜索是否已有答案</p>
</div>
</div>
</div>
</div>
</template>
<div v-else class="gvs-card p-16 text-center text-gray-400">
帖子不存在或已删除
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { communityApi, type CommunityPostDetail } from '@/api/community'
import '@/assets/community.css'
const route = useRoute()
const router = useRouter()
const post = ref<CommunityPostDetail | null>(null)
const loading = ref(false)
const submitting = ref(false)
const replyContent = ref('')
const liked = ref(false)
const isLoggedIn = ref(false)
async function loadPost(): Promise<void> {
const uuid = route.params.uuid as string
if (!uuid) return
loading.value = true
try {
post.value = await communityApi.post(uuid)
liked.value = post.value.Liked === 1
} catch {
post.value = null
} finally {
loading.value = false
}
}
async function toggleLike(): Promise<void> {
if (!post.value) return
try {
const r = await communityApi.like(post.value.PostUUID)
liked.value = r.Liked === 1
post.value.LikeCount = Math.max(0, post.value.LikeCount + (r.Liked === 1 ? 1 : -1))
} catch (e) {
alert((e as Error).message || '操作失败')
}
}
async function submitReply(): Promise<void> {
if (!post.value || !replyContent.value.trim()) return
submitting.value = true
try {
await communityApi.reply({ PostUUID: post.value.PostUUID, Content: replyContent.value.trim() })
replyContent.value = ''
await loadPost()
} catch (e) {
alert((e as Error).message || '回复失败')
} finally {
submitting.value = false
}
}
async function detectLogin(): Promise<void> {
try {
const res = await fetch('https://ugm.gvsds.com/Query/GetELTUserAvatar', { credentials: 'include' })
isLoggedIn.value = res.ok
} catch {
isLoggedIn.value = false
}
}
/** 头像加载失败时隐藏(露出底层 fa-user 图标) */
function hideAvatar(e: Event): void {
;(e.target as HTMLImageElement).style.display = 'none'
}
onMounted(() => {
detectLogin()
loadPost()
})
</script>