3 changed files with 225 additions and 2 deletions
@ -0,0 +1,217 @@ |
|||
<script setup lang="ts"> |
|||
import { ref, onMounted } from 'vue' |
|||
import { commentApi } from '@/api/client' |
|||
import type { CommentResponse } from '@/generated/api' |
|||
|
|||
const props = defineProps<{ |
|||
slug: string |
|||
}>() |
|||
|
|||
const comments = ref<CommentResponse[]>([]) |
|||
const loading = ref(false) |
|||
const submitting = ref(false) |
|||
const error = ref<string | null>(null) |
|||
const submitMessage = ref<string | null>(null) |
|||
|
|||
const authorName = ref('') |
|||
const authorEmail = ref('') |
|||
const content = ref('') |
|||
|
|||
function formatDate(iso?: string): string { |
|||
if (!iso) return '' |
|||
const d = new Date(iso) |
|||
if (isNaN(d.getTime())) return iso |
|||
return d.toLocaleString('zh-CN', { hour12: false }) |
|||
} |
|||
|
|||
async function load() { |
|||
if (!props.slug) return |
|||
loading.value = true |
|||
error.value = null |
|||
try { |
|||
const resp = await commentApi.apiArticlesSlugCommentsGet(props.slug) |
|||
comments.value = resp.data.data ?? [] |
|||
} catch (e) { |
|||
console.error(e) |
|||
error.value = '评论加载失败' |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
async function onSubmit() { |
|||
if (!authorName.value.trim() || !content.value.trim()) { |
|||
error.value = '昵称和内容为必填项' |
|||
return |
|||
} |
|||
submitting.value = true |
|||
error.value = null |
|||
submitMessage.value = null |
|||
try { |
|||
await commentApi.apiArticlesSlugCommentsPost(props.slug, { |
|||
authorName: authorName.value.trim(), |
|||
authorEmail: authorEmail.value.trim() || undefined, |
|||
content: content.value.trim() |
|||
}) |
|||
content.value = '' |
|||
submitMessage.value = '评论已提交,等待审核' |
|||
load() |
|||
} catch (e) { |
|||
console.error(e) |
|||
error.value = '评论提交失败' |
|||
} finally { |
|||
submitting.value = false |
|||
} |
|||
} |
|||
|
|||
onMounted(load) |
|||
</script> |
|||
|
|||
<template> |
|||
<section class="comment-section"> |
|||
<h3 class="comment-title">评论</h3> |
|||
<p v-if="loading" class="hint-text">加载中...</p> |
|||
<p v-else-if="comments.length === 0" class="hint-text">暂无评论</p> |
|||
<ul v-else class="comment-list"> |
|||
<li v-for="comment in comments" :key="comment.id" class="comment-item"> |
|||
<div class="comment-meta"> |
|||
<span class="comment-author">{{ comment.authorName }}</span> |
|||
<span class="comment-time">{{ formatDate(comment.createdAt) }}</span> |
|||
</div> |
|||
<p class="comment-content">{{ comment.content }}</p> |
|||
</li> |
|||
</ul> |
|||
|
|||
<form class="comment-form" @submit.prevent="onSubmit"> |
|||
<h4 class="comment-form-title">发表评论</h4> |
|||
<p v-if="error" class="error-text">{{ error }}</p> |
|||
<p v-if="submitMessage" class="success-text">{{ submitMessage }}</p> |
|||
<label class="form-label" for="comment-author">昵称</label> |
|||
<input id="comment-author" v-model="authorName" class="form-input" type="text" maxlength="100" /> |
|||
<label class="form-label" for="comment-email">邮箱(可选)</label> |
|||
<input id="comment-email" v-model="authorEmail" class="form-input" type="email" /> |
|||
<label class="form-label" for="comment-content">内容</label> |
|||
<textarea |
|||
id="comment-content" |
|||
v-model="content" |
|||
class="form-textarea" |
|||
rows="3" |
|||
maxlength="2000" |
|||
></textarea> |
|||
<button class="submit-btn" type="submit" :disabled="submitting"> |
|||
{{ submitting ? '提交中...' : '提交评论' }} |
|||
</button> |
|||
</form> |
|||
</section> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
.comment-section { |
|||
margin-top: 32px; |
|||
} |
|||
|
|||
.comment-title { |
|||
font-size: 18px; |
|||
margin: 0 0 12px; |
|||
} |
|||
|
|||
.comment-list { |
|||
list-style: none; |
|||
padding: 0; |
|||
margin: 0 0 20px; |
|||
} |
|||
|
|||
.comment-item { |
|||
padding: 12px 0; |
|||
border-bottom: 1px solid #eee; |
|||
} |
|||
|
|||
.comment-item:last-child { |
|||
border-bottom: none; |
|||
} |
|||
|
|||
.comment-meta { |
|||
display: flex; |
|||
gap: 12px; |
|||
align-items: baseline; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.comment-author { |
|||
font-weight: 600; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.comment-time { |
|||
color: #8a93a3; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.comment-content { |
|||
margin: 0; |
|||
font-size: 14px; |
|||
color: #333; |
|||
} |
|||
|
|||
.comment-form { |
|||
background: #fff; |
|||
border: 1px solid #e5e5ea; |
|||
border-radius: 8px; |
|||
padding: 16px; |
|||
} |
|||
|
|||
.comment-form-title { |
|||
margin: 0 0 12px; |
|||
font-size: 15px; |
|||
} |
|||
|
|||
.form-label { |
|||
display: block; |
|||
margin: 10px 0 4px; |
|||
font-size: 13px; |
|||
color: #5c6470; |
|||
} |
|||
|
|||
.form-input, |
|||
.form-textarea { |
|||
width: 100%; |
|||
padding: 8px 10px; |
|||
border: 1px solid #d3d8e0; |
|||
border-radius: 6px; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.form-textarea { |
|||
resize: vertical; |
|||
font-family: inherit; |
|||
} |
|||
|
|||
.submit-btn { |
|||
margin-top: 12px; |
|||
padding: 8px 18px; |
|||
border: none; |
|||
border-radius: 6px; |
|||
background: #3b82f6; |
|||
color: #fff; |
|||
font-size: 14px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.submit-btn:disabled { |
|||
opacity: 0.6; |
|||
} |
|||
|
|||
.error-text { |
|||
color: #d33c3c; |
|||
font-size: 13px; |
|||
} |
|||
|
|||
.success-text { |
|||
color: #1e7e34; |
|||
font-size: 13px; |
|||
} |
|||
|
|||
.hint-text { |
|||
color: #8a93a3; |
|||
} |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue