formula project
This commit is contained in:
249
frontend/src/views/CommentDetail.vue
Executable file
249
frontend/src/views/CommentDetail.vue
Executable file
@@ -0,0 +1,249 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { fetchCommentThread, postReply, type CommentItem } from '@/api/comments'
|
||||
import { isAuthenticated } from '@/utils/auth'
|
||||
import { userInfoStore } from '@/store/UserInfo'
|
||||
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userInfo = userInfoStore()
|
||||
const user_id = ref(-1)
|
||||
|
||||
const id = Number(route.params.id)
|
||||
|
||||
const root = ref<any>({})
|
||||
const replies = ref<any>([])
|
||||
const loading = ref(false)
|
||||
|
||||
const load = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await fetchCommentThread(id)
|
||||
replies.value = Array.isArray(data['replies']) ? data['replies'] : []
|
||||
console.log(replies.value)
|
||||
root.value = data['parent']
|
||||
await userInfo.ensureUserInfoLoaded()
|
||||
user_id.value = userInfo.userInfo.id
|
||||
} catch (e: any) {
|
||||
// 占位数据,便于前后端未对接时验证界面
|
||||
root.value = { id, user: 'Alex', time: '刚刚', content: '这是原始评论' }
|
||||
replies.value = [
|
||||
{ id: id * 10 + 1, user: 'Mia', time: '1 分钟前', content: '同意你的观点', parentId: id, toUser: 'Alex' },
|
||||
{ id: id * 10 + 2, user: 'Ken', time: '2 分钟前', content: '我有不同看法', parentId: id, toUser: 'Mia' }
|
||||
]
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
|
||||
const replyText = ref('')
|
||||
const replyPosting = ref(false)
|
||||
const replyTo = ref<any | null>(null)
|
||||
|
||||
const doReply = (c: any) => {
|
||||
if (!isAuthenticated()) {
|
||||
ElMessage.warning('请先登录后再回复')
|
||||
return
|
||||
}
|
||||
replyTo.value = c
|
||||
}
|
||||
|
||||
const submitReply = async () => {
|
||||
if (!replyTo.value || !replyText.value.trim()) {
|
||||
ElMessage.warning('请输入回复内容')
|
||||
return
|
||||
}
|
||||
replyPosting.value = true
|
||||
try {
|
||||
await postReply(Number(route.params.id), replyText.value.trim(), user_id.value, replyTo.value.id)
|
||||
ElMessage.success('回复成功')
|
||||
const data = await fetchCommentThread(id)
|
||||
replies.value = Array.isArray(data['replies']) ? data['replies'] : []
|
||||
replyText.value = ''
|
||||
replyTo.value = null
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '回复失败')
|
||||
} finally {
|
||||
replyPosting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getReplyContent = (reply_id: number) => {
|
||||
const r = replies.value.find((item: any) => item.id === reply_id)
|
||||
return r?.content || root.value.content
|
||||
}
|
||||
|
||||
const deleteRootCom = async (id: number) => {
|
||||
if (!isAuthenticated()) {
|
||||
ElMessage.warning('请先登录后再删除评论')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm('确定删除该评论?删除后该页面将不存在', '确认删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await fetch(`/api/comments/${id}`, { method: 'DELETE', credentials: 'include' })
|
||||
ElMessage.success('删除成功')
|
||||
router.push('/')
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteRepCom = async (id: number) => {
|
||||
if (!isAuthenticated()) {
|
||||
ElMessage.warning('请先登录后再删除评论')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm('确定删除该评论?删除后相关评论将同时删除', '确认删除', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await fetch(`/api/comments/${id}`, { method: 'DELETE', credentials: 'include' })
|
||||
ElMessage.success('删除成功')
|
||||
await load()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '删除失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="comment-detail" v-loading="loading">
|
||||
<el-card class="root-card">
|
||||
<div class="comment-header">
|
||||
<div class="user-basinfo">
|
||||
<el-avatar :size="32">{{ root?.username?.[0] || '?' }}</el-avatar>
|
||||
<div class="comment-user">{{ root?.username }}</div>
|
||||
</div>
|
||||
<el-button v-if="root.user_id === user_id" type="danger" class="delete" @click="deleteRootCom(root.id)"><el-icon>
|
||||
<Delete />
|
||||
</el-icon></el-button>
|
||||
</div>
|
||||
<div class="comment-content">{{ root?.content }}</div>
|
||||
<div class="reply-actions">
|
||||
<el-button size="small" @click="doReply(root)">回复</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<h3 class="reply-title" style="color:white;">全部回复</h3>
|
||||
<div class="replies">
|
||||
<el-card v-for="r in replies" :key="r.id" class="reply-card" shadow="never">
|
||||
<div class="reply-meta">{{ r.username }} 回复 {{ r.reply_to_username }} : {{
|
||||
getReplyContent(r.response_id).slice(0, 5) + (getReplyContent(r.response_id).length > 5 ? '...' : '') }}</div>
|
||||
<div class="comment-header">
|
||||
<div class="user-basinfo">
|
||||
<el-avatar :size="28">{{ r.username?.[0] || '?' }}</el-avatar>
|
||||
<div class="comment-user">{{ r.username }}</div>
|
||||
</div>
|
||||
<el-button v-if="r.user_id === user_id" type="danger" class="delete" @click="deleteRepCom(r.id)"><el-icon>
|
||||
<Delete />
|
||||
</el-icon></el-button>
|
||||
</div>
|
||||
<div class="comment-content">{{ r.content }}</div>
|
||||
<div class="reply-actions">
|
||||
<el-button size="small" @click="doReply(r)">回复</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div class="reply-compose">
|
||||
<el-input v-model="replyText" type="textarea" :rows="3"
|
||||
:placeholder="replyTo ? '回复 ' + (replyTo.username || '') : '选择要回复的评论后输入...'" />
|
||||
<div class="compose-actions">
|
||||
<el-button type="primary" :loading="replyPosting" @click="submitReply">发表回复</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.comment-detail {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.root-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.user-basinfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.comment-user {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.comment-time {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.reply-title {
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
|
||||
.replies {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.reply-card {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.reply-meta {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.reply-actions {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.reply-compose {
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.compose-actions {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user